[JSOI2008]最小生成树计数

Description

  现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的
最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生
成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

  第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整
数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,0
00。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

  输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Sample Input

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1

Sample Output

8
**定理一:**如果 $A, B$ 同为 $G$ 的最小生成树,且 $A$ 的边权从小到大为 $w(a_1), w(a_2), w(a_3), \cdots w(a_n)$,$B$ 的边权从小到大为 $w(b_1), w(b_2), w(b_3), \cdots w(b_n)$,则有 $w(a_i) = w(b_i)$。
**定理二:**如果 $A, B$ 同为 $G$ 的最小生成树,如果 $A, B$ 都从零开始从小到大加边($A$ 加 $A$ 的边,$B$ 加 $B$ 的边)的话,每种权值加完后图的联通性相同。
假设边权为w的边有很多
那么每种生成树的w边数是一样的
根据定理2
只要算出每种边权下的方案相乘就行了
算完后给树加边,我们无论怎麽分配w的边,只要尽可能放完就行了
不会因为分配不同就影响到后面大于w的边的分配
 
假设s1,s2,s3都是之前加的小边构成的树,他们构成一个森林
虚线是边权为w,当前要分配的边
把s1,s2,s3缩成一个点
 
当前的分配方案就是这个无向图的生成树个数
可以dfs,也可以矩阵树(dfs做法见此)
求完后直接往森林中随便加3条边
但是要考虑这个新图无法建成生成树
如果是这样的情况,虽然没有把森林联通,但显然也要算方案,但是矩阵树算法会算出0
所以还要把这个新图加桥,使得正好联通,不会改变矩阵树算出来的方案
要判断无解的情况
  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 #include<cmath>
  6 using namespace std;
  7 struct Node
  8 {
  9   int u,v,d;
 10 }edge[2001],edg[2001];
 11 int Mod=31011;
 12 int a[101][101],set[101],tmp[101],set2[101],ans=1,n,m,sum;
 13 bool cmp(Node a,Node b)
 14 {
 15   return a.d<b.d;
 16 }
 17 int gi()
 18 {
 19   char ch=getchar();
 20   int x=0;
 21   while (ch<'0'||ch>'9') ch=getchar();
 22   while (ch>='0'&&ch<='9')
 23     {
 24       x=x*10+ch-'0';
 25       ch=getchar();
 26     }
 27   return x;
 28 }
 29 int find(int x)
 30 {
 31   if (set[x]!=x) set[x]=find(set[x]);
 32   return set[x];
 33 }
 34 int find2(int x)
 35 {
 36   if (set2[x]!=x) set2[x]=find2(set2[x]);
 37   return set2[x];
 38 }
 39 int guass(int S)
 40 {int i,j,k,as=0;
 41   S--;
 42   for (i=1;i<=S;i++)
 43     {
 44       for (j=1;j<=S;j++)
 45     {
 46       a[i][j]=(a[i][j]+Mod)%Mod;
 47     }
 48     }
 49   as=1;
 50   for (i=1;i<=S;i++)
 51     {
 52       for (j=i+1;j<=S;j++)
 53     while (a[j][i])
 54       {
 55         int t=a[i][i]/a[j][i];
 56         for (k=i;k<=S;k++)
 57           {
 58           a[i][k]=(a[i][k]-1ll*t*a[j][k]%Mod+Mod)%Mod;
 59           swap(a[i][k],a[j][k]);
 60           }
 61         as*=-1;
 62       }
 63       as=1ll*as*a[i][i]%Mod;;
 64     }
 65   return (as+Mod)%Mod;
 66 }
 67 void cal(int st,int ed)
 68 {int cnt,i;
 69   cnt=0;
 70   for (i=st;i<=ed;i++)
 71     {
 72       edg[i]=edge[i];
 73       int p=find(edg[i].u),q=find(edg[i].v);
 74       edg[i].u=p;edg[i].v=q;
 75       if (p==q) continue;
 76       tmp[++cnt]=edg[i].u;
 77       tmp[++cnt]=edg[i].v;
 78     }
 79   sort(tmp+1,tmp+cnt+1);
 80   cnt=unique(tmp+1,tmp+cnt+1)-tmp-1;
 81   memset(a,0,sizeof(a));
 82   for (i=1;i<=cnt;i++)
 83     set2[i]=i;
 84   for (i=st;i<=ed;i++)
 85     {
 86       if (edg[i].u==edg[i].v) continue;
 87       int p=find(edg[i].u),q=find(edg[i].v);
 88       if (p!=q)
 89     {
 90       --sum;
 91       set[p]=q;
 92     }
 93       int u=lower_bound(tmp+1,tmp+cnt+1,edg[i].u)-tmp;
 94       int v=lower_bound(tmp+1,tmp+cnt+1,edg[i].v)-tmp;
 95       a[u][u]++;a[v][v]++;
 96       a[u][v]--;a[v][u]--;
 97       p=find2(u),q=find2(v);
 98       if (p!=q) set2[p]=q;
 99     }
100   for (i=2;i<=cnt;i++)
101     if (find2(i)!=find2(i-1))
102       {
103       int p=find2(i),q=find2(i-1);
104       a[p][p]++;a[q][q]++;
105       a[p][q]--;a[q][p]--;
106       if (p!=q) set2[p]=q;
107       }
108   ans=1ll*ans*guass(cnt)%Mod;
109 }
110 int main()
111 {int i;
112   cin>>n>>m;
113   for (i=1;i<=m;i++)
114     {
115       edge[i].u=gi();edge[i].v=gi();edge[i].d=gi();
116     }
117   sort(edge+1,edge+m+1,cmp);
118   for (i=1;i<=n;i++)
119     set[i]=i;
120   sum=n;
121   int j;
122   for (i=1;i<=m;i=j)
123     {
124       for (j=i;j<=m;j++)
125       if (edge[j].d!=edge[i].d) break;
126       if (j-i>1) cal(i,j-1);
127       else
128       {
129         int p=find(edge[i].u),q=find(edge[i].v);
130         if (p!=q)
131           {
132             set[p]=q;
133             --sum;
134           }
135       }
136     }
137   if (sum>1) printf("0");
138   else printf("%d",ans);
139 }

 

转载于:https://www.cnblogs.com/Y-E-T-I/p/8462255.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值