【BZOJ】【1016】【JSOI2008】最小生成树计数
Kruskal/并查集+枚举
唉我还是too naive,orz Hzwer
一开始我是想:最小生成树删掉一条边,再加上一条边仍是最小生成树,那么这两条边权值必须相等,但我也可以去掉两条权值为1和3的,再加上权值为2和2的,不也满足题意吗?事实上,如果这样的话……最小生成树应该是1和2,而不是1和3或2和2!!!
所以呢?所以对于一个图来说,最小生成树有几条边权为多少的边,都是固定的!所以我们可以做一遍Kruskal找出这些边权,以及每种边权出现的次数。然后,对于每种边权,比方说出现了$v_i$次,那么可以替换的一定是形成环的!且这种边权的选取方案对其他边权的没有影响,我就可以$2^{v_i}$枚举每条边是否选取,暴力找出这$v_i$条边的可行方案数,这个复杂度并不高,因为题目保证了这里的$v_i \leq 10$。
proverbs:
最小生成树的两个性质:
1、边权相等的边的个数一定。
2、做完边权为w的所有边时,图的连通性相同。
1 /************************************************************** 2 Problem: 1016 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:8 ms 7 Memory:1300 kb 8 ****************************************************************/ 9 10 //BZOJ 1016 11 #include<vector> 12 #include<cstdio> 13 #include<cstring> 14 #include<cstdlib> 15 #include<iostream> 16 #include<algorithm> 17 #define rep(i,n) for(int i=0;i<n;++i) 18 #define F(i,j,n) for(int i=j;i<=n;++i) 19 #define D(i,j,n) for(int i=j;i>=n;--i) 20 #define pb push_back 21 using namespace std; 22 inline int getint(){ 23 int v=0,sign=1; char ch=getchar(); 24 while(ch<'0'||ch>'9'){ if (ch=='-') sign=-1; ch=getchar();} 25 while(ch>='0'&&ch<='9'){ v=v*10+ch-'0'; ch=getchar();} 26 return v*sign; 27 } 28 const int N=110,INF=~0u>>2,MOD=31011; 29 typedef long long LL; 30 /******************tamplate*********************/ 31 int n,m,fa[N]; 32 struct edge{int u,v,w;}e[1010]; 33 struct data{int l,r,v;}a[1010]; 34 bool cmp(edge a,edge b){return a.w<b.w;} 35 int Find(int x){return fa[x]==x?x:Find(fa[x]);} 36 int sum,ans=1,tot; 37 void dfs(int x,int now,int k){ 38 if (now==a[x].r+1){ 39 if (k==a[x].v) sum++; 40 return; 41 } 42 int p=Find(e[now].u),q=Find(e[now].v); 43 if (p!=q){ 44 fa[p]=q; 45 dfs(x,now+1,k+1); 46 fa[p]=p; fa[q]=q; 47 } 48 dfs(x,now+1,k); 49 } 50 int main(){ 51 #ifndef ONLINE_JUDGE 52 freopen("1016.in","r",stdin); 53 freopen("1016.out","w",stdout); 54 #endif 55 n=getint(); m=getint(); 56 F(i,1,m){ 57 e[i].u=getint(); e[i].v=getint(); e[i].w=getint(); 58 } 59 sort(e+1,e+m+1,cmp); 60 int cnt=0,now=0; 61 F(i,1,n) fa[i]=i; 62 F(i,1,m){ 63 if (i==1||e[i].w!=e[i-1].w) {a[++cnt].l=i;a[cnt-1].r=i-1;} 64 int p=Find(e[i].u),q=Find(e[i].v); 65 if (p!=q){ fa[p]=q; a[cnt].v++; tot++;} 66 } 67 a[cnt].r=m; 68 if (tot!=n-1){puts("0");return 0;} 69 F(i,1,n) fa[i]=i; 70 F(i,1,cnt){ 71 sum=0; 72 dfs(i,a[i].l,0); 73 ans=ans*sum%MOD; 74 F(j,a[i].l,a[i].r){ 75 int p=Find(e[j].u),q=Find(e[j].v); 76 if (p!=q) fa[p]=q; 77 } 78 } 79 printf("%d\n",ans); 80 return 0; 81 }
1016: [JSOI2008]最小生成树计数
Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 3312 Solved: 1311
[Submit][Status][Discuss]
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,000。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过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
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1
Sample Output
8