luogu 4208 [JSOI2008]最小生成树计数

题目描述

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

分析

很妙的一道暴搜(?)题

是的,暴搜,真是一个优秀的算法

正解什么矩阵树,高斯消元啥的

我暴搜流派就是流弊


先放几个写得不错的题解:

题解1hzwer大佬

题解2luogu题解

题解3神奇的证明

怎么搜,首先一个结论:

对于最小生成树的一个替代边,必有替代边等于最小生成树中的一边,并与这一边连接了同两个连通块

所以,对于最小生成树的不同方案,相同长度的边的个数是一样的

我们可以对相同长度的边进行暴搜(选/不选),最后判断是否与最小生成树中的个数相同来判断是否合法‘’

用乘法原理,将各个不同长度的边的方案相乘得到最终方案数

  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 }
View Code

 

为你,所向披靡!

posted @ 2019-09-14 08:44  Mandy_H_Y  阅读(237)  评论(0编辑  收藏  举报