JSOI2008 最小生成树计数

题目链接

题目解析

刚开始想到了加边然后破圈法,但我似乎不会统计答案。

有个结论:所有(MST)中,同一权值的边的个数是不会变的。

简单说明一下:

我们想想(Kruskal)的算法流程,是按照边权从小到大进行排序加边,然后直到整个图联通我们就得到了(MST)。如果再选一个(MST)出来,假设我们删掉其中一条边,为了满足权值的要求,我们必须找一个权值恰好等于这条边的边来替代它。

而会不会出现我去掉两条边,然后加一条更小的边,再加一条更大的边,最后的结果是一样的呢?不会出现这种情况的。如果去掉的这两条边,可以用后面加的这两条边代替的话,而我们知道每断开(MST)上一条边,就会把树变成两半,那么为了保持树的形态,更小的那条边应该是可以替代被换下来的两条边的某一条边的(指连通性),那么这样可以得到一个更小的(MST),矛盾了。

接下来我们可以利用这个结论干点事情:

(而且题目也说了具有相同权值的边不会超过10条,复杂度不会超过(2^{10} imes frac{1000}{10})

我们可以针对每种权值的边进行搜索:要不要这条边?类似于求(MST)的过程,在(MST)里的边都承担起联通两个连通块的功能,所以如果一条边的两个结点不在同一连通块里,可以选择要/不要这条边,否则就只能不要这条边。这里要注意回溯的写法(详见代码

(为了方便描述,我下面用“颜色”来表示离散化后的边的权值

如果一个方案的这种颜色的边的条数恰好等于(MST)中这个颜色的边的条数,那么这就是一个合法方案。所有颜色合法方案的乘积就是答案。

还可以有一个小剪枝:如果当前加入的边的条数大于了(MST)里的边的条数,显然不合法,(return)掉。

在算完一种颜色的边之后,要把这种颜色的边全部并在一起,保证后面的连通性判断正确,这个顺序应该是“无伤大雅”的,因为我们知道每种颜色有多少条边在(MST)里面,加了那么多条边之后就可以了,毕竟我们只需要(MST)中的任意一种情况来判断连通性。


►Code View

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define N 105
#define M 1005
#define MOD 31011
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return f*x;
}
struct node{
	int u,v,w;
}edge[M];
int n,m,f[N];
pair<int,int> a[M];//每种权值的边的范围 
int c[M]/*第i条边的颜色(权值离散化下标)*/,tot/*权值总数*/;
int num[M];//第i种权值的边的条数 
int ans=1,res;
bool cmp(node p,node q)
{
	return p.w<q.w;
}
void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]==x) return x;
	return f[x]=Find(f[x]);
}
bool Union(int u,int v)
{
	u=Find(u),v=Find(v);
	if(u==v) return 0;
	if(u<v) f[u]=v;
	else f[v]=u;
	return 1;
}
void Kruskal()
{
	Init();
	int cnt=0,sum=0;
	for(int i=1;i<=m;i++)
	{
		if(Union(edge[i].u,edge[i].v))
		{
			num[c[i]]++;
			cnt++;
			sum+=edge[i].w;
		}
		if(cnt==n-1) break;
	}
	if(cnt!=n-1)
	{
		puts("0");
		exit(0);
	}
}
void dfs(int i,int k,int x)
{//当前搜到第i条边 已有k条边 为第x种权值
	if(k>num[x]) return ;
	if(i==a[x].second+1)
	{
		if(k==num[x]) res++;
		return ;
	}
	int p[N];//不能定义成全局变量 递归搜索下去之后值就变了 
	for(int j=1;j<=n;j++)
		p[j]=f[j];
	if(Union(edge[i].u,edge[i].v))
		dfs(i+1,k+1,x);
	for(int j=1;j<=n;j++)
		f[j]=p[j];
	dfs(i+1,k,x); 
}
int main()
{
	n=rd(),m=rd();
	for(int i=1;i<=m;i++)
		edge[i].u=rd(),edge[i].v=rd(),edge[i].w=rd();
	sort(edge+1,edge+m+1,cmp);
	c[1]=++tot,a[1].first=a[1].second=1;
	for(int i=2;i<=m;i++)//找同一权值的边的范围
	{
		if(edge[i].w==edge[i-1].w)
			a[tot].second++,c[i]=tot;
		else
		{
			c[i]=++tot;
			a[tot].first=a[tot].second=i;
		}
	}
	Kruskal();
	Init();
	for(int i=1;i<=tot;i++)
		if(num[i])
		{
			res=0;
			dfs(a[i].first,0,i);
			ans=ans*res%MOD;
			int cnt=0;
			for(int j=a[i].first;j<=a[i].second;j++)
			{
				if(Union(edge[j].u,edge[j].v)) cnt++;
				if(cnt==num[i]) break;
			}
		}
	printf("%d
",ans);
	return 0;
}


原文地址:https://www.cnblogs.com/lyttt/p/14032121.html