指挥网络

树形图的定义:没有环,每个点(除了根节点)的入度都是1,根节点的入度为0

朱刘算法的过程见OI-wiki;当没有环的时候,就满足了树形图的定义,于是可以结束;否则的话就将所有环缩点(注意此时由于每个点的入度都是1,所以不可能存在两个环有公共点和公共边)得到新图G1,对于G1中的边,有三类:如果是缩点点内部的边(也就是环上的边)那么就删去,如果是终点在缩点点内部的边(也就是终点是环上的点),那么边权为原边权减去终点所选择的那条入边的权值,如果是其余边就不变

比如

image

变成

image

最终答案就是所有选的边的边权和

证明:如果最开始的选择之后无环,那么肯定是最小树形图(达到了下界);否则的话,先看看两个引理

引理一:对图中任意一个环,至少去掉一条边

这个显然

引理二:存在一个最小树形图,使得每个环只去掉一条边

证:如果对任意一个最小树形图,都存在一个环至少去掉两条边,我们找出这个环,假设这个环是x0x1···xkx0,不妨设xkx0xixi+1这两条边被去掉了;对当前的最小树形图,肯定可以从跟走到x0再从x0走到xi,现在考虑xi+1的入边,我们将这条入边删掉,换成(xi,xi+1),显然还是一个最小树形图,而且边权和变小,矛盾;于是得证

根据引理二,我们找出原图中所有满足每个环只去掉一条边的树形图,设他们组成的集合是S,那么S中边权和最小的就是答案;我们再考虑将原图的环缩点之后得到的新图G1的所有最小树形图组成的集合是S1,不难知道SS1一一对应(包括权值都是相同的),于是转化成求G1的最小树形图;所以递归求解就好了

注意下面的代码,想成是一个基环树(但是根节点没有入边,所以可能有两个连通块)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1e4+10,N=110;
int n,m,r;
struct node
{
	int u,v,w;
}e[M];
int in[N],id[N],pre[N];
int vis[N];
int solve(int rt)
{
	int ans=0;
	while(1)
	{
		for(int i=1;i<=n;i++) in[i]=1e9;//in[i]表示i的入边边权 
		for(int i=1;i<=m;i++)
		if(e[i].u!=e[i].v&&e[i].w<in[e[i].v])
		{
			in[e[i].v]=e[i].w;
			pre[e[i].v]=e[i].u;//pre[v]表示v的入边的起点 
		}
		for(int i=1;i<=n;i++) 
		if(i!=rt&&in[i]==1e9) return -1;//如果不连通就无解 
		int cnt=0;
		memset(id,0,sizeof(id));//id[i]表示点i缩点之后的编号 
		memset(vis,0,sizeof(vis));//vis[i]表示点是否访问过
		//vis不能为bool类型
		//这是因为如果我们从根所在连通块的某个点出发找环 
		//是找不到的
		//但是如果vis为bool类型
		//那么第二次从根所在连通块的某个点出发找环的时候
		//就会进入 if(v!=rt&&!id[v]) 这个语句
		//这样子就错了 
		//看不懂可以看洛谷模板题“最小树形图”的第一个样例 
		in[rt]=0;
		for(int i=1;i<=n;i++)
		{
			ans+=in[i];
			int v=i;
			while(vis[v]!=i&&!id[v]&&v!=rt)
			{
				vis[v]=i;
				v=pre[v];
			}
			if(v!=rt&&!id[v])//如果不是根节点所在连通块 
			{
				cnt++;
				for(int u=pre[v];u!=v;u=pre[u]) id[u]=cnt;
        		id[v]=cnt;
        		i++;
        		for(;i<=n;i++) ans+=in[i];//将剩余边权全部加上 
			}
		}
		if(!cnt) return ans;//没有环了 
		for(int i=1;i<=n;i++) if(!id[i]) id[i]=++cnt;//重新编号 
		for(int i=1;i<=m;i++)
		{
			e[i].w-=in[e[i].v];//调整边权
			//这里第三类边也要调整,因为我们的ans是累加的 
			e[i].u=id[e[i].u],e[i].v=id[e[i].v];
		}
		n=cnt;//调整数量 
		rt=id[rt];
	}
}
int main()
{
    scanf("%d%d%d",&n,&m,&r);
    for(int i=1;i<=m;i++)
    scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    printf("%d",solve(r));
    return 0;
}
posted @   最爱丁珰  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示