【BZOJ 1016】【JSOI 2008】最小生成树计数

8 篇文章 0 订阅
6 篇文章 0 订阅

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


就是不同的最小生成树方案,每种权值的边的数量是确定的,每种权值的边的作用是确定的

排序以后先做一遍最小生成树,得出每种权值的边使用的数量x

然后对于每一种权值的边搜索,得出每一种权值的边选择方案

然后乘法原理

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 31011
using namespace std;
int n,m,cnt,tot,ans=1,sum;
int fa[105];
struct edge{int x,y,v;}e[1005];
struct data{int l,r,v;}a[1005];
inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
bool cmp(edge a,edge b){return a.v<b.v;}
int find(int x){return x==fa[x]?x:find(fa[x]);}
void dfs(int x,int now,int k)
{
     if(now==a[x].r+1)
     {
         if(k==a[x].v)sum++;
         return;
     }
     int p=find(e[now].x),q=find(e[now].y);
     if(p!=q)
     {
         fa[p]=q;
         dfs(x,now+1,k+1);
         fa[p]=p;fa[q]=q;
     }
     dfs(x,now+1,k);
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
        e[i].x=read(),e[i].y=read(),e[i].v=read();
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        if(e[i].v!=e[i-1].v){a[++cnt].l=i;a[cnt-1].r=i-1;}
        int p=find(e[i].x),q=find(e[i].y);
        if(p!=q){fa[p]=q;a[cnt].v++;tot++;}
    }
    a[cnt].r=m;
    if(tot!=n-1){printf("0");return 0;}
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=cnt;i++)
    {
        sum=0;
        dfs(i,a[i].l,0);
        ans=(ans*sum)%mod;
        for(int j=a[i].l;j<=a[i].r;j++)
        {
            int p=find(e[j].x),q=find(e[j].y);
            if(p!=q)fa[p]=q;
        }
    }
    printf("%d",ans);
    return 0;
}


该算法用2^N次方来搜索sum值,当相同权值的边过多时,很有可能超时
还有一种算法能有效的避免该问题

O(N^3)

/*
 *题目大意:
 *给出一个简单无向加权图,求这个图中有多少个不同的最小生成树;
 *由于不同的最小生成树可能很多,所以只需输出方案数对31011的模就可以了;
 *
 *算法思想:
 *Kruskal+Matrix_Tree定理;
 *
 *先按照任意顺序对等长的边进行排序;
 *然后利用并查集将所有长度为L0的边的处理当作一个阶段来整体看待;
 *可以定义一个数组的vector向量来保存每一个连通块的边的信息;
 *即将原图划分成多个连通块,每个连通块里面的边的权值都相同;
 *针对每一个连通块构建对应的Kirchhoff矩阵C,利用Matrix_Tree定理求每一个连通块的生成树个数;
 *最后把他们的值相乘即可;
 *
 *Matrix_Tree定理:
 *G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n-1阶主子式的行列式的绝对值;
 *n-1阶主子式就是对于r(1≤r≤n),将C[G]的第r行,第r列同时去掉后得到的新矩阵,用Cr[G]表示;
 **/

/****************************************************************/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 111;
const int M = 1111;
const int mod = 31011;
struct Edges {
	int a, b, c;
	bool operator<(const Edges & x) const {
		return c < x.c;
	}
} edge[M];
int n, m;
int f[N], U[N], vist[N]; //f,U都是并查集,U是每组边临时使用
int G[N][N], C[N][N]; //G顶点之间的关系,C为生成树计数用的Kirchhoff矩阵
vector<int> V[N]; //记录每个连通分量
int Find(int x, int f[]) {
	if (x == f[x])
		return x;
	else
		return Find(f[x], f);
}
int det(int a[][N], int n) //生成树计数:Matrix-Tree定理
		{
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			a[i][j] %= mod;
	int ret = 1;
	for (int i = 1; i < n; i++) {
		for (int j = i + 1; j < n; j++)
			while (a[j][i]) {
				int t = a[i][i] / a[j][i];
				for (int k = i; k < n; k++)
					a[i][k] = (a[i][k] - a[j][k] * t) % mod;
				for (int k = i; k < n; k++)
					swap(a[i][k], a[j][k]);
				ret = -ret;
			}
		if (a[i][i] == 0)
			return 0;
		ret = ret * a[i][i] % mod;
	}
	if (ret < 0)
		ret = -ret;
	return (ret + mod) % mod;
}
void Solve() {
	sort(edge, edge + m); //按权值排序
	for (int i = 1; i <= n; i++) //初始化并查集
			{
		f[i] = i;
		vist[i] = 0;
	}
	int Edge = -1; //记录相同的权值的边
	int ans = 1;
	for (int k = 0; k <= m; k++) {
		if (edge[k].c != Edge || k == m) //一组相等的边,即权值都为Edge的边加完
				{
			for (int i = 1; i <= n; i++) {
				if (vist[i]) {
					int u = Find(i, U);
					V[u].push_back(i);
					vist[i] = 0;
				}
			}
			for (int i = 1; i <= n; i++) //枚举每个连通分量
					{
				if (V[i].size() > 1) {
					for (int a = 1; a <= n; a++)
						for (int b = 1; b <= n; b++)
							C[a][b] = 0;
					int len = V[i].size();
					for (int a = 0; a < len; a++) //构建Kirchhoff矩阵C
						for (int b = a + 1; b < len; b++) {
							int a1 = V[i][a];
							int b1 = V[i][b];
							C[a][b] = (C[b][a] -= G[a1][b1]);
							C[a][a] += G[a1][b1]; //连通分量的度
							C[b][b] += G[a1][b1];
						}
					int ret = (int) det(C, len);
					ans = (ans * ret) % mod; //对V中的每一个连通块求生成树个数再相乘
					for (int a = 0; a < len; a++)
						f[V[i][a]] = i;
				}
			}
			for (int i = 1; i <= n; i++) {
				U[i] = f[i] = Find(i, f);
				V[i].clear();
			}
			if (k == m)
				break;
			Edge = edge[k].c;
		}
		int a = edge[k].a;
		int b = edge[k].b;
		int a1 = Find(a, f);
		int b1 = Find(b, f);
		if (a1 == b1)
			continue;
		vist[a1] = vist[b1] = 1;
		U[Find(a1, U)] = Find(b1, U); //并查集操作
		G[a1][b1]++;
		G[b1][a1]++;
	}
	int flag = 0;
	for (int i = 2; i <= n && !flag; i++)
		if (U[i] != U[i - 1])
			flag = 1;
	if (m == 0)
		flag = 1;
	printf("%d\n", flag ? 0 : ans % mod);
}
int main() {
	while (~scanf("%d%d", &n, &m)) {
		memset(G, 0, sizeof(G));
		for (int i = 1; i <= n; i++)
			V[i].clear();
		for (int i = 0; i < m; i++)
			scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].c);
		Solve();
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值