1016: [JSOI2008]最小生成树计数
Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 7429 Solved: 3098
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
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1
Sample Output
题意:
给你一个无向连通图,求生成树权值和最小的生成树的个数
题解:
一开始看错了数据,直接状压了每种权值不同边的取舍情况并在树上倍增维护(x,y)简单路径上最大权值累加方案数。
果不其然拿到了0pts。
实际上并不需要这么复杂。
注意到权值相同的边最多不超过10条,
那么我们考虑MST(最小生成树)的两个性质:
1.每棵MST每种边权的边使用的数量相同。
附带证明(转):
证:设最小生成树有n条边,任意两棵最小生成树分别称为A, B, 如果e是一条边,用w(e)表示该边的权值。
A的边按权值递增排序后为a1, a2,……an满足w(a1)≤w(a2)≤……w(an)
B的边按权值递增排序后为b1, b2,……bn满足w(b1)≤w(b2)≤……w(bn)
设i是两个边列表中,第一次出现不同边的位置,ai≠bi,不妨设w(ai)≥w(bi)。
情形1 如果树A中包含边bi,则一定有j>i使得 bi=aj ,事实上,这时有 w(bi)=w(aj)≥w(ai) ≥w(bi) 故 w(bi)=w(aj)=w(ai),在树A的边列表中交换边ai和 aj的位置并不会影响树A的边权有序列表,两棵树在第i个位置的边变成同一条边。
情形2 树A中并不包含边bi,则把bi加到树A上,形成一个环,由于A是最小生成树,这个环里任意一条边的权值都不大于w(bi) ,另外,这个环里存在边aj不在树B中。因此,有w(aj)≤w(bi),且j>i (因为aj不在B中)。于是,有w(bi)≤w(ai)≤w(aj)≤w(bi),因此
w(ai)= w(aj) = w(bi)。那么在树A中把aj换成bi仍然保持它是一棵最小生成树,并不会影响树A的边权有序列表,并且转换成情形1。
综上,任意两棵最小生成树A,B的边按权值递增排序后的结果相同,即每种边权的边使用数量相同。
2.每棵MST内每种边连出的联通块相同。
证:考虑Kruskal算法的过程,对于边权等于v的边集{e},如果eu与ev不在同一个联通块中则连接eu,ev。
即对于每条边,若它沟通的两个点不在边权小于v的边集连出的同一个联通块内,则该边连且必须连。
根据假设,边权小于v的边集连出的联通块是唯一确定的,那么边权等于v连出的联通块也是唯一确定的。
得到了这两个性质再回头看这道题显然十分简单。
先求一遍MST,得出权值为v的边使用的数量num(v)。
由于具有相同权值的边数<=10,我们可以将所有边按权值v(从小到大)分块,
状压维护每一块中的边的取舍状态并判断该状态是否满足MST性质,
也就是边数=num(v)并且没有连出环。边数直接count二进制中1的个数,环用并查集维护。
最终答案即为每个块中满足要求的状态数之积。(乘法原理)
注意每次处理完一个块要把它连成联通块,不然无法判断下一个块内的状态是否形成环。
代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define MAXN 100005 #define MAXM 500005 #define INF 0x7fffffff #define ll long long #define MOD 31011 struct edge{ll u,v,w;}e[MAXM]; struct node{ll l,r,n;}s[MAXM]; ll f[MAXN],tf[MAXN]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } bool cmp(edge a,edge b) {return a.w<b.w;} ll find(ll x) {return f[x]==x?x:f[x]=find(f[x]);} ll calc(ll x) {ll ans=0;while(x)ans+=(x&1)?1:0,x>>=1;return ans;} ll find1(ll x) {return tf[x]==x?x:tf[x]=find1(tf[x]);} bool solve(ll x,ll y,ll N,ll l){ ll cnt=0,cnt1=0; while(x){ if(x&1){ ll u=e[l+cnt].u,v=e[l+cnt].v; ll t1=find1(u),t2=find1(v); if(t1!=t2) tf[t2]=t1,cnt1++; } x>>=1;cnt++; } return cnt1==y; } int main(){ ll N=read(),M=read(); for(ll i=1;i<=M;i++) e[i].u=read(),e[i].v=read(),e[i].w=read(); sort(e+1,e+1+M,cmp); for(ll i=1;i<=N;i++) f[i]=i; ll cnt=0,num=0,maxn=0; for(ll i=1;i<=M;i++){ ll t1=find(e[i].u),t2=find(e[i].v); if(e[i].w!=e[i-1].w) {s[cnt].r=i-1;s[++cnt].l=i;} if(t1!=t2) f[t2]=t1,s[cnt].n+=1,num++; } if(num!=N-1) {printf("%d\n",0);return 0;} for(ll i=1;i<=N;i++) f[i]=i; s[cnt].r=M; ll sum=1; for(ll i=1;i<=cnt;i++){ //cout<<s[i].l<<" "<<s[i].r<<" "<<s[i].n<<endl; if(!s[i].n) continue; ll j=s[i].l,k=s[i].r,now=0; for(ll sta=0;sta<(1<<k-j+1);sta++){ if(calc(sta)!=s[i].n) continue; memcpy(tf,f,sizeof(f)); if(solve(sta,s[i].n,N,s[i].l)) now++; } //memcpy(f,tf,sizeof(tf)); //cout<<now<<endl; sum*=now;sum%=MOD; for(ll z=j;z<=k;z++){ ll t1=find(e[z].u),t2=find(e[z].v); if(t1!=t2) f[t2]=t1; } } printf("%lld\n",sum); return 0; }