• 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;
    }
    
    
    
  • 相关阅读:
    linux 内核升级4.19
    监管对保险页面的要求
    软件测试-测试可交付成果
    软件测试架构思想
    dockerfile
    转载:.NET Core 图片操作在 Linux/Docker 下的坑
    docker build速度过慢问题
    .net 5 发布到 docker 或 docker 镜像方法
    Centos 安装 docker 教程
    DQL、DML、DDL、DCL全名是啥?
  • 原文地址:https://www.cnblogs.com/lyttt/p/14032121.html
Copyright © 2020-2023  润新知