欢迎访问~原文出处——博客园-zhouzhendong
去博客园看该题解
题目传送门 - BZOJ1016
题意概括
现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。
答案对于31011取模。
题解
先考虑错误的prim——
这个是我的第一感,拿到题目,prim一发,发现错了。至于为什么,童鞋们可以根据下面的正解找一找反例。
然而答案是kruskal。
我们发现,一个图的最小生成树,如果有多种,那么无论如何,把他们各自的连边按照边权排序之后,序列在数值和数值个数上一定是相同的。
也就是说,如果在方案A中,我们共取了x条权值为y的边,那么在方案B中,一定有且仅有x条权值为y的边。
那么怎么证明?
博主大蒟蒻实在太弱,难以给出好的证明。下面只是博主的一面之词。
假设最小生成树的边集为E,对于其中的一个子集e,我们假设有边集e',|e| = |e'|,(我们假设e[i].z为边i的权值)且Σe[i].z = Σe'[i].z , 而且,e构成的连通分量中的节点和e'相同。
那么,必然存在一种方案e'',使得e''在满足以上条件的基础上,改变取边方案,使得Σe''[i].z < Σe[i].z,那么之前的假设就失败了。
太笼统了?
是的,想知道明细的童鞋可以—— 百度一下,你就知道。
然后,对于每一类边权相同的边,我们dfs一下有多少种方案,然后乘法原理即可。
题目保证边权相同的边数不超过10。
那么,为什么可以独立呢?
那么我们就要保证dfs出来的方案起到的效果是一定的。
(对于同一边权)
假设方案A可以达到连接某些节点的效果,方案B可以达到连接另一些节点的效果。
那么必然存在方案C,使得效果更佳。
代码
#include <cstring> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cmath> #include <vector> using namespace std; typedef long long LL; const int mod=31011,N=100+5,M=1000+5; int n,m,tot,fa[N],cnt[M]; struct Edge{ int x,y,z; }e[M]; vector <int> id[M]; bool cmp(Edge a,Edge b){ return a.z<b.z; } int getf(int k){ return fa[k]==k?k:getf(fa[k]); } int dfs(int bh,int pos,int chosen){ if (pos>=id[bh].size()) return chosen==cnt[bh]; int ans=dfs(bh,pos+1,chosen),ID=id[bh][pos]; int x=getf(e[ID].x),y=getf(e[ID].y); if (x==y) return ans; fa[x]=y; ans=(ans+dfs(bh,pos+1,chosen+1))%mod; fa[x]=x; return ans; } int main(){ scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z); sort(e+1,e+m+1,cmp); for (int i=1;i<=n;i++) fa[i]=i; memset(cnt,0,sizeof cnt); int tote=tot=0; for (int i=1;i<=m;i++){ if (e[i].z!=e[i-1].z) tot++; id[tot].push_back(i); int x=getf(e[i].x),y=getf(e[i].y); if (x==y) continue; cnt[tot]++; fa[x]=y; tote++; } if (tote<n-1){ puts("0"); return 0; } for (int i=1;i<=n;i++) fa[i]=i; int ans=1; for (int i=1;i<=tot;i++){ ans=1LL*ans*dfs(i,0,0)%mod; for (int j=0;j<id[i].size();j++){ int ID=id[i][j]; int x=getf(e[ID].x),y=getf(e[ID].y); if (x==y) continue; fa[x]=y; } } printf("%d",ans); return 0; }