BZOJ 1016[JSOI2008]最小生成树计数
问一个图最小生成树的个数,n<100,m<1000,规定相同权值的边不超过10条。
每天午觉起来很长一段时间都仿佛活在梦中。上午看的下午来打,狂RE不止,发现一种边只有一条的情况没有r会GG。。
//Twenty #include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<queue> #include<vector> typedef long long LL; const int maxn=100+5; const int maxm=1000+5; const int mod=31011; using namespace std; int ans=1,sum,n,m,fa[maxn],ecnt,tot; struct edge{ int u,v,l,r,w,o; friend bool operator <(const edge &A,const edge &B) { return A.w<B.w; } }e[maxm],ee[maxm]; void init() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); } int find(int x) {return x==fa[x]?x:find(fa[x]);} void kruskal() { sort(e+1,e+m+1); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) { int u=e[i].u,v=e[i].v; if(tot==n-1&&e[i].w!=ee[ecnt].w) break; u=find(u),v=find(v); if(!ecnt||e[i].w!=ee[ecnt].w) { ee[++ecnt].l=i; ee[ecnt].w=e[i].w; } ee[ecnt].r=i; if(u!=v) { fa[u]=v; tot++; ee[ecnt].o++; } } } void dfs(int id,int now,int k) { if(now==ee[id].r+1&&k==ee[id].o) sum++; if(now==ee[id].r+1) return ; int res=0; int u=find(e[now].u),v=find(e[now].v); if(u!=v) { fa[u]=v; dfs(id,now+1,k+1); fa[u]=u; fa[v]=v; } dfs(id,now+1,k); } void work() { kruskal(); if(tot!=n-1) {printf("0"); return ;} for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=ecnt;i++) { sum=0; dfs(i,ee[i].l,0); ans=(ans*sum)%mod; for(int j=ee[i].l;j<=ee[i].r;j++) { int u=find(e[j].u),v=find(e[j].v); if(u!=v) fa[u]=v; } } printf("%d\n",ans); } int main() { init(); work(); return 0; }
最小生成树的两个性质:
(1)不同的最小生成树中,每种权值的边出现的个数是确定的
(2)不同的生成树中,某一种权值的边连接完成后,形成的联通块状态是一样的
有了这两个性质,先跑一遍Kruskal求出最小生成树和每种权值的边分别出现的次数,因为相同权值的边很少,对于每种权值的边直接爆搜,乘法原理。
若是没有这个次数限制,就要用矩阵树定理解决。