【JSOI 2008】【BZOJ 1016】最小生成数计数

这题题目中有一个很显眼的提示,每种权值的边不会超过10条,这提示我们可以采用些暴力方法。

首先在每个最小生成树中有两个结论:

1、每种权值的边数相等。

2、每种权值所选边构建后图的联通形态相同。

1比较好理解,若1不成立,则最小生成树总权值不固定。

2可以通过Kruskal算法流程来理解。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct hq{
    int sum;
    int num[1001];
}stack[1001],tot[1001];
struct hp{
    int u,v,w;
}a[1001];
int father[1001],now[1001],fathernow[1001],fatherlast[1001];
int b[1001],n,m,size,sum=0,t=0,ans=1;
int cmp(const hp &a,const hp &b)
{
    if (a.w<b.w) return 1;
    else return 0;
}
int findnow(int x)
{
    if (x!=fathernow[x])
      fathernow[x]=findnow(fathernow[x]);
    return fathernow[x];
}
int find(int x)
{
    if (x!=father[x])
      father[x]=find(father[x]);
    return father[x];
}
bool judge()
{
    int i,r1,r2;
    bool f=false;
    for (i=1;i<=n;++i)
      fathernow[i]=fatherlast[i];
    for (i=1;i<=t;++i)
      {
        r1=findnow(a[now[i]].u);
        r2=findnow(a[now[i]].v);
        if (r1<r2)
          fathernow[r1]=fathernow[r2];
        else
          {
            if (r2<r1)
              fathernow[r2]=fathernow[r1];
            else
              f=true;
          }
      }
    for (i=1;i<=n;++i)
      findnow(i),find(i);
    for (i=1;i<=n;++i)
      if (fathernow[i]!=father[i])
        f=true;
    for (i=1;i<=n;++i)
      fathernow[i]=fatherlast[i];
    if (f)
      return false;
    else
      return true;
}
void work(int wgt,int i,int last)
{
    int j;
    if (i==tot[wgt].sum+1)
      {
        if (judge())
          sum=(sum+1)%31011;
        return;
      }
    for (j=last+1;j<=stack[wgt].sum;++j)
      {
        now[++t]=stack[wgt].num[j];
        work(wgt,i+1,j);
        --t;
      }
}
int main()
{
    int i,r1,r2,j,k=0,wgt=0;
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;++i)
      {
        scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);
        b[i]=a[i].w;
      }
    sort(b+1,b+m+1);
    size=unique(b+1,+b+m+1)-b-1;
    for (i=1;i<=m;++i)
      a[i].w=upper_bound(b+1,b+size+1,a[i].w)-b-1;
    sort(a+1,a+m+1,cmp);
    for (i=1;i<=m;++i)
      stack[a[i].w].num[++stack[a[i].w].sum]=i;
    for (i=1;i<=n;++i)
      father[i]=i;
    for (i=1;i<=m;++i)
      {
        r1=find(a[i].u); r2=find(a[i].v);
        if (r1!=r2)
          {
            tot[a[i].w].num[++tot[a[i].w].sum]=i;
            wgt+=a[i].w;
            father[r1]=r2;
            k++;
            size=a[i].w;
          }
        if (k==n-1)
          break;
      }
    if (k!=n-1)
      printf("0\n");
    else
      {
        for (i=1;i<=n;++i)
          father[i]=fatherlast[i]=i;
        for (i=1;i<=size;++i)
          {
            for (j=1;j<=tot[i].sum;++j)
              {
                r1=find(a[tot[i].num[j]].u);
                r2=find(a[tot[i].num[j]].v);
                if (r1<r2)
                  father[r1]=father[r2];
                if (r2<r1)
                  father[r2]=father[r1];
              }
            memset(now,0,sizeof(now));
            t=0; sum=0;
            work(i,1,0);
            ans=(ans*sum)%31011;
            for (j=1;j<=n;++j)
              fatherlast[j]=father[j];
          }
        printf("%d\n",ans);
      }
}
时间: 2024-08-25 22:53:15

【JSOI 2008】【BZOJ 1016】最小生成数计数的相关文章

JSOI 2008 最小生成树计数

JSOI 2008 最小生成树计数 今天的题目终于良心一点辣 一个套路+模版题. 考虑昨天讲的那几个结论,我们有当我们只保留最小生成树中权值不超过 $ k $ 的边的时候形成的联通块是一定的. 我们可以先拿 kruskal 跑一棵最小生成树,然后我们可以从小到大枚举边权,把所有除开枚举到的边权的边全部加入并且缩点.现在我们就在这个缩点后的点集进行生成树计数就好了.答案就是每种边权算出答案的积. 因为我们知道,连入 $ k $ 边权的边后对于 $ 1 $ 到 $ k - 1 $ 的边加入后的最小生

[BZOJ 1016] [JSOI2008] 最小生成树计数 【DFS】

题目链接:BZOJ - 1016 题目分析 最小生成树的两个性质: 同一个图的最小生成树,满足: 1)同一种权值的边的个数相等 2)用Kruscal按照从小到大,处理完某一种权值的所有边后,图的连通性相等 这样,先做一次Kruscal求出每种权值的边的条数,再按照权值从小到大,对每种边进行 DFS, 求出这种权值的边有几种选法. 最后根据乘法原理将各种边的选法数乘起来就可以了. 特别注意:在DFS中为了在向下DFS之后消除决策影响,恢复f[]数组之前的状态,在DFS中调用的Find()函数不能路

BZOJ 1977 次小生成树(最近公共祖先)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1977 题意:求一棵树的严格次小生成树,即权值严格大于最小生成树且权值最小的生成树. 思路:若现在已经得到了最小生成树,那么若 添加一条边E,就会得到一个环,我们只需要去掉环上权值小于E且最大的一条边就会得到另一棵较优的生成树.因此,只需要枚举不在生成树上的边,计算将其添 加到最小生成树中得到的新生成树的权值.取最小值即可.那么,现在的问题就是在一个圈中找到一个最大的小于新添加的边的权值

[POI 2008][BZOJ 1132]Tro

这题我真是无能为力了 这题的做法还是挺简单的 枚举左下角的点做为原点,把其余点按极角排序    PS.是作为原点,如枚举到 k 时,对于所有 p[i] (包括p[k]) p[i]-=p[k] (此处为向量减法) 排序后满足 i<j 的两个向量 p[i] 和 p[j] 的叉积都是正数了 ΣΣp[i]×p[j] = ΣΣ(p[i].x*p[j].y-p[i].y*p[j].x) = Σ(p[i].x*Σp[j].y)-Σ(p[i].y*Σp[j].x) 计算叉积和的复杂度就从 O(n2) 降为了 O

bzoj 2111: [ZJOI2010]Perm 排列计数 (dp+卢卡斯定理)

bzoj 2111: [ZJOI2010]Perm 排列计数 1 ≤ N ≤ 10^6, P≤ 10^9 题意:求1~N的排列有多少种小根堆 1: #include<cstdio> 2: using namespace std; 3: const int N = 1e6+5; 4: typedef long long LL; 5: LL m, p, T, x, y, F[N]; 6: LL n, size[N<<1]; 7: LL f[N]; 8: LL inv(LL t, LL

BZOJ 1016 JSOI 2008 最小生成树计数 Kruskal+搜索

题目大意:给出一些边,求出一共能形成多少个最小生成树. 思路:最小生成树有非常多定理啊,我也不是非常明确.这里仅仅简单讲讲做法.关于定各种定理请看这里:http://blog.csdn.net/wyfcyx_forever/article/details/40182739 我们先做一次最小生成树.然后记录每一种长度的边有多少在最小生成树中,然后从小到大搜索,看每一种边权有多少种放法.然后全部的都算出来累乘就是终于的结果. CODE: #include <map> #include <cs

BZOJ 1016 最小生成树计数

Description 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的).由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了. Input 第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数.每个节点用1~n的整数编号.接下来的m行,每行包含两个整数:a, b, c,表示节点a

BZOJ 1016: [JSOI2008]最小生成树计数

http://www.lydsy.com/JudgeOnline/problem.php?id=1016 题意: 思路: 一个无向图所有的最小生成树中某种权值的边的数目均相同. 引用一篇大牛的证明: 我们证明以下定理:一个无向图所有的最小生成树中某种权值的边的数目均相同. 开始时,每个点单独构成一个集合. 首先只考虑权值最小的边,将它们全部添加进图中,并去掉环,由于是全部尝试添加,那么只要是用这种权值的边能够连通的点,最终就一定能在一个集合中. 那么不管添加的是哪些边,最终形成的集合数都是一定的

【JSOI 2008】【BZOJ 1014】火星人prefix

这题其实很奇怪,一眼看出fhq treap+字符串hash... 结果在BZOJ被卡常了,结果学长告诉我了一些优化,有些自己还不会写,只写了inline和吧unsigned long long改为unsigned int... 结果时间少了一半,谁能告诉我为什么... #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm>