JSOI2008 最小生成树计数
Time Limit: 1 Sec Memory Limit: 162 MB
Description
现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的
最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。
Input
第一行包含两个数,\(n\)和\(m\),其中\(1\leq n\leq100\); \(1\leq m\leq1000\); 表示该无向图的节点数和边数。每个节点用1~n的整数编号。
接下来的m行,每行包含两个整数:\(a\),\(b\),\(c\),表示节点\(a\),\(b\)之间的边的权值为\(c\),其中\(1\leq c \leq 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
Sample Output
8
Solution
这道题目的思路其实也比较新颖,还是要多练习练习。做题目还是要逐一分析所有性质啊。
我们可以发现,对于同一种边权,在任何的最小生成树中的数量都是一定的。这个挺好证的,就不详细说了。而且事实上,同一种边权在所有最小生成树里面的作用都是一样的——它们连接成联通快一定是一样的。
我们可以从Kruskal的正确性上连看出这个性质。从小到大排序,逐一加边,这样一种边权全都加完,已经尽量地连接上了这个边权所能连接上的尽量多的联通快了,这个已经是这个边权的极限了。我们假如同一种边权有两种不同的连接方法能把不同的两组点连接起来,那么我们显然可以合并一下,得到更大的联通块。因此,同一种边权在所有最小生成树永远都是连接同一组点的。
同时,不用的边权的作用也是相互独立的。这个性质非常显然,因为上面已经得到,之前的比它小的边权只能得到一个联通快,那么这一个边权就可以直接把那个联通块视为一个点,然后找他自己的东西了。
那么我们就得到了做法:先跑一边Kruskal,求出每一种边权的使用次数。然后在每一种边权内部,直接搜索有多少种方案。因为同一种边权不超过10个,所以复杂度得到了保证。
不过这里深搜需要回溯,所以我们的并查集不能路径压缩,不然不方便撤销。直接按秩合并,可以保证复杂度的\(\log\)。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define lowbit(x) ((x)&(-(x)))
#define REP(i,a,n) for(register int i=(a);i<=(n);++i)
#define PER(i,a,n) for(register int i=(a);i>=(n);--i)
#define FEC(i,x) for(register int i=head[x];i;i=g[i].ne)
#define dbg(...) fprintf(stderr,__VA_ARGS__)
namespace io{
const int SIZE=(1<<21)+1;char ibuf[SIZE],*iS,*iT,obuf[SIZE],*oS=obuf,*oT=oS+SIZE-1,c,qu[55];int f,qr;
#define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),(iS==iT?EOF:*iS++)):*iS++)
inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
inline void putc(char x){*oS++=x;if(oS==oT)flush();}
template<class I>inline void read(I &x){for(f=1,c=gc();c<'0'||c>'9';c=gc())if(c=='-')f=-1;for(x=0;c<='9'&&c>='0';c=gc())x=x*10+(c&15);x*=f;}
template<class I>inline void write(I x){if(!x)putc('0');if(x<0)putc('-'),x=-x;while(x)qu[++qr]=x%10+'0',x/=10;while(qr)putc(qu[qr--]);}
struct Flusher_{~Flusher_(){flush();}}io_flusher_;
}//orz laofudasuan
using io::read;using io::putc;using io::write;
typedef long long ll;typedef unsigned long long ull;
template<typename A,typename B>inline bool SMAX(A&x,const B&y){return y>x?x=y,1:0;}
template<typename A,typename B>inline bool SMIN(A&x,const B&y){return y<x?x=y,1:0;}
const int N=100+7,M=1000+7,MOD=31011;
int n,m,b[M],chk[M],g[M][11],cnt,ans,dis;//错误笔记:chk数组应该开到M的规模
struct Graph{int x,y,z;inline bool operator<(const Graph&a)const{return z<a.z;}}G[M];
inline int Bound(int x){int l=1,r=dis;while(l<r){int mid=(l+r)>>1;if(x<=b[mid])r=mid;else l=mid+1;}return l;}
inline void LSH(){
sort(G+1,G+m+1);for(register int i=1;i<=m;++i)if(i==1||G[i].z!=G[i-1].z)b[++dis]=G[i].z;
for(register int i=1;i<=m;++i)G[i].z=Bound(G[i].z),g[G[i].z][++g[G[i].z][0]]=i;
}
int fa[N],num[N];
inline int Find(int x){while(fa[x]!=x)x=fa[x];return x;}
inline void Union(int x,int y){x=Find(x),y=Find(y);if(num[x]<num[y])x^=y^=x^=y;fa[y]=x;SMAX(num[x],num[y]+1);}
inline void Kruskal(){
for(register int i=1;i<=n;++i)fa[i]=i,num[i]=1;
int cnt=0;for(register int i=1;i<=m;++i){
int x=Find(G[i].x),y=Find(G[i].y),z=G[i].z;
if(x==y)continue;++chk[z];Union(x,y);++cnt;if(cnt==n-1)break;
}
if(cnt!=n-1){write(0),putc('\n');exit(0);}
}
inline void DFS(int x,int i,int j){
if(i==g[x][0]+1&&j!=chk[x])return;if(g[x][0]-i+1<chk[x]-j)return;
if(j==chk[x])return void(++cnt);
DFS(x,i+1,j);
int a=Find(G[g[x][i]].x),b=Find(G[g[x][i]].y),sa=num[a],sb=num[b];
if(a==b)return;else Union(a,b);
DFS(x,i+1,j+1);fa[a]=a,fa[b]=b,num[a]=sa,num[b]=sb;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("BZOJ1016.in","r",stdin);freopen("BZOJ1016.out","w",stdout);
#endif
read(n),read(m);
for(register int i=1;i<=m;++i)read(G[i].x),read(G[i].y),read(G[i].z);
LSH();Kruskal();
for(register int i=1;i<=n;++i)fa[i]=i,num[i]=1;
ans=1;for(register int i=1;i<=dis;++i)if(chk[i]){//错误笔记:这里应赋值fa[j]=j不是fa[j]=i
cnt=0;DFS(i,1,0);(ans*=cnt)%=MOD;
for(register int j=1;j<=g[i][0];++j){int x=Find(G[g[i][j]].x),y=Find(G[g[i][j]].y);if(x!=y)Union(x,y);}//错误笔记:这里需要做一下并查集,把这一下已经连接好的连一下。
}
write(ans),putc('\n');
}