【JSOI2008】【BZOJ1016】最小生成树计数
我就爱写矩阵树定理!!!
就不写暴力!!!
1016: [JSOI2008]最小生成树计数
Time Limit: 1 Sec Memory Limit: 162 MB
Submit: 3584 Solved: 1429
[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
Sample Output
8
HINT
Source
边权把全部边排序
边权同样的边构成连通块
每一个连通块做一遍矩阵树
答案相乘
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define P 31011
#define MAXN 110
#define MAXINT 0x7fffffff
using namespace std;
int C[MAXN][MAXN],D[MAXN][MAXN],A[MAXN][MAXN];
int block[MAXN][MAXN],top[MAXN];
long long ans=1;
int n,m;
bool vis[MAXN];
struct Set
{
int f[MAXN];
int find(int x)
{
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
void Union(int x,int y)
{
int a=find(x),b=find(y);
f[b]=a;
}
}s1,s2;//对每一个连通块用s2,整体用s1
struct edge
{
int u,v,w;
bool operator <(const edge& a)const
{
return w<a.w;
}
}e[MAXN*10];
void in(int &x)
{
char ch=getchar();x=0;
while (!(ch>='0'&&ch<='9')) ch=getchar();
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
int calc(int size)
{
int ret=1;
for (int i=1;i<=size;i++)
for (int j=1;j<=size;j++)
C[i][j]=(C[i][j]+P)%P;
for (int i=1;i<=size;i++)
{
for (int j=i+1;j<=size;j++)
{
int a=C[i][i],b=C[j][i];
while (b)
{
int t=a/b;a%=b;swap(a,b);
for (int k=i;k<=size;k++) C[i][k]=(C[i][k]-C[j][k]*t)%P;
for (int k=i;k<=size;k++) swap(C[i][k],C[j][k]);
ret=-ret;
}
}
if (!C[i][i]) return 0;
ret*=C[i][i];ret%=P;
}
return (ret+P)%P;
}
void print()
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++) cout<<C[i][j]<<' ';
cout<<endl;
}
}
void Print()
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++) cout<<A[i][j]<<' ';
cout<<endl;
}
cout<<endl;
}
int main()
{
in(n);in(m);
if (m<n-1)
{
cout<<0<<endl;
return 0;
}
for (int i=1;i<=m;i++) in(e[i].u),in(e[i].v),in(e[i].w);
for (int i=1;i<=n;i++) s1.f[i]=s2.f[i]=i;
sort(e+1,e+m+1);
int t=-MAXINT;
for (int i=1;i<=m+1;i++)
{
if (e[i].w!=t||i>m)//边权不同,计算行列式值,进入下一个连通块
{
for (int j=1;j<=n;j++)
if (vis[j])
{
int F=s2.find(j);
block[F][++top[F]]=j;
vis[j]=0;
}
for (int j=1;j<=n;j++)
if (top[j]>1)
{
memset(C,0,sizeof(C));
for (int k=1;k<=top[j];k++)//对连通块构建矩阵
for (int l=k+1;l<=top[j];l++)
{
int a=block[j][k],b=block[j][l];
C[k][l]=(C[l][k]-=A[a][b]);
C[k][k]+=A[a][b];C[l][l]+=A[a][b];
}
ans=(ans%P*calc(top[j]-1))%P;
for (int k=1;k<=top[j];k++) s1.f[block[j][k]]=j;
}
for (int j=1;j<=n;j++)
{
s1.f[j]=s2.f[j]=s1.find(j);
top[j]=0;memset(block[j],0,sizeof(block[j]));
}
if (i>m) break;
t=e[i].w;
}
int x=s1.find(e[i].u),y=s1.find(e[i].v);
if (x==y) continue;
vis[x]=vis[y]=1;
s2.Union(x,y);
D[x][x]++;D[y][y]++;A[x][y]++;A[y][x]++;
}
cout<<ans<<endl;
}