题目描述
现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。
输入格式
第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。
接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。
数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。
输出格式
输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。
输入输出样例
输入 #1
4 6 1 2 1 1 3 1 1 4 1 2 3 2 2 4 1 3 4 1
输出 #1
8
说明/提示
说明 1<=n<=100; 1<=m<=1000;1≤ci≤109
分析
很妙的一道暴搜(?)题
是的,暴搜,真是一个优秀的算法
正解什么矩阵树,高斯消元啥的
我暴搜流派就是流弊
先放几个写得不错的题解:
怎么搜,首先一个结论:
对于最小生成树的一个替代边,必有替代边等于最小生成树中的一边,并与这一边连接了同两个连通块
所以,对于最小生成树的不同方案,相同长度的边的个数是一样的
我们可以对相同长度的边进行暴搜(选/不选),最后判断是否与最小生成树中的个数相同来判断是否合法‘’
用乘法原理,将各个不同长度的边的方案相乘得到最终方案数
1 /************************** 2 User:Mandy.H.Y 3 Language:c++ 4 Problem:luogu 5 Algorithm: 6 **************************/ 7 #include<bits/stdc++.h> 8 9 using namespace std; 10 11 const int maxn = 105; 12 const int maxm = 1005; 13 const int mod = 31011; 14 15 int n,m,size,tot; 16 int father[maxn]; 17 int cnt,ans,sum; 18 int son[maxn]; 19 20 struct Node{ 21 int l,r,num; 22 }a[maxm]; 23 24 struct Edge{ 25 int u,v,w; 26 }edge[maxm]; 27 28 template<class T>inline void read(T &x){ 29 x = 0;char ch = getchar();bool flag = 0; 30 while(!isdigit(ch)) flag |= ch == '-',ch = getchar(); 31 while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar(); 32 if(flag) x = -x; 33 } 34 35 void file(){ 36 freopen("award10.in","r",stdin); 37 } 38 39 void eadd(int u,int v,int w){ 40 edge[++size].v = v; 41 edge[size].u = u; 42 edge[size].w = w; 43 } 44 45 bool cmp(const Edge &a,const Edge &b){ 46 return a.w < b.w; 47 } 48 49 void readdata(){ 50 read(n);read(m); 51 for(int i = 1;i <= m; ++ i){ 52 int u,v,w; 53 read(u);read(v);read(w); 54 eadd(u,v,w); 55 } 56 sort(edge + 1,edge + m + 1,cmp); 57 } 58 59 int find1(int x){//压缩路径 60 return father[x] == x ? x : father[x] = find1(father[x]); 61 } 62 63 int find2(int x){//不压缩路径 64 return father[x] == x ? x : find2(father[x]); 65 } 66 67 void merge(int x,int y){ 68 father[find1(x)] = find1(y); 69 } 70 71 void merge1(int x,int y){ 72 if(son[x] > son[y]){ 73 father[y] = x; 74 son[x] += son[y]; 75 son[y] = son[x]; 76 } else { 77 father[x] = y; 78 son[y] += son[x]; 79 son[x] = son[y]; 80 } 81 }//按秩合并 82 83 void dfs(int id,int pos,int num){ 84 //id - 边的长度的编号 85 //pos - 边的编号 86 //num - 这条边在最小生成树中的个数 87 if(pos == a[id].r + 1){//到边了 88 if(num == a[id].num) sum++; 89 return;//如果不等于在最小生成树中的个数,说明方案错了 90 } 91 int u = find2(edge[pos].u); 92 int v = find2(edge[pos].v);//不能压缩路径 93 if(u != v){ 94 father[u] = v; 95 dfs(id,pos+1,num +1);//选这条边 96 father[u] = u; 97 } 98 dfs(id,pos + 1,num);//不选这条边 99 } 100 101 void work(){ 102 edge[0].w = -10000; 103 edge[m+1].w = -10000; 104 for(int i = 1;i <= n; ++ i) father[i] = i; 105 for(int i = 1;i <= m; ++ i){ 106 int u = find1(edge[i].u); 107 int v = find1(edge[i].v); 108 if(edge[i-1].w != edge[i].w){ 109 a[cnt].r = i-1; 110 a[++cnt].l = i;//a数组中存下相同长度的边的左右边界 111 } 112 if(u != v){ 113 a[cnt].num++;//存下这个长度的边在最小生成树中的个数 114 merge(u,v); 115 ++tot; 116 } 117 } 118 a[cnt].r = m;//给最后一个加上右边界 119 if(tot != n-1) { 120 puts("0"); 121 return; 122 } 123 for(int i = 1;i <= n; ++ i) father[i] = i; 124 for(int i = 1;i <= n; ++ i) son[i] = 1; 125 ans = 1;//初始化 126 for(int i = 1;i <= cnt; ++ i){ 127 sum = 0; 128 dfs(i,a[i].l,0); 129 ans = ans * sum % mod; 130 for(int j = a[i].l;j <= a[i].r; ++ j){ 131 int fx = find2(edge[j].u); 132 int fy = find2(edge[j].v);//按最小生成树连边 133 if(fx != fy) merge1(fx,fy); 134 } 135 } 136 printf("%d",ans); 137 } 138 139 int main(){ 140 // file(); 141 readdata(); 142 work(); 143 return 0; 144 }
为你,所向披靡!