最小树形图/朱刘算法……表示稍微记录一下

本文详细介绍了一种求解最小树形图问题的算法流程,包括寻找权值最小的入边、判断图的连通性、查找并压缩环路等步骤,并通过具体的代码实现展示了如何更新节点间的距离。

大概就是那么几步……呃…

1) 每个点找到权值最小的入边,记录为In[i];

2) 【假装是一只定根的图】判有无除了根节点之外的独立节点,如果有的话那这个图必然不连通 于是直接return -1

3) 然后开始找环,至于在这种有n条边的图上找环的事,参考noip2015提高组D1T2

4) 找环的时候顺手把环缩了(标记一下,创建新的节点)

5) 然后更新 新图 的每个节点之间的距离,于是就会出现下面这种鬼畜的东西↓↓↓


说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。                           ——摘自百度百科 最小树形图(看代码看不懂的那一段


关于为什么 在给点重新编号的时候,计算每个新的点之间的距离 是这样奇怪的公式

假设是点i到点集vj(也就是新点vj)的距离,vj由n个点组成(vj1,vj2,vj3.....vjn)

dis[i][vj]=min{dis[i][vjk]-in[vik]}(1<=k<=n)

但是点集vj到点i的距离却是

dis[vj][i]=min{dis[vjk][i]}(1<=k<=n)

相当于是每条边都减去它指向的那个点的最小入边长度(如果这条边两边的点不在同一个集合中的话)


6) 最后,默默表示,细节什么的,真的,很重要 orzorzorz,用左闭右开区间,【左闭右闭WA到半夜一点的某表示……对自己的信仰产生了怀疑


最后附上呆马,虽然丑哭了,也许还会又WA又T又RE

题目在……呃……这里的learn那道题

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;	int n,m;
int S=0;
const int INF=0x3f3f3f3f;

int aim[55];
int num[55],cnt_node=1;
struct t1{
	int frm,to,nxt,lth;
}edge[10057];	int cnt_edge=0;
int fst[557];
void addedge(int x,int y,int z){
	edge[++cnt_edge].to=y;
	edge[cnt_edge].frm=x;
	edge[cnt_edge].nxt=fst[x];
	edge[cnt_edge].lth=z;
	fst[x]=cnt_edge;
}

void init(){
	for(int i=1;i<=n;++i){
		for(int j=num[i]+1;j<num[i+1];++j)	
			addedge(j,j-1,0);
		addedge(num[n+1],num[i],0);
	}
	while(m--){
		int a,b,c,d,e;
		scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
		addedge(num[a]+b,num[c]+d,e);
	}
}
//========================================================================
int ans=0;
int col[557];
int cnt_col;

int pre[557];
int In[557];
int vis[557];

int zhuliu(int root,int NE,int NV){
	for(;;){
		memset(In,INF,sizeof(In));
		memset(pre,-1,sizeof(pre));

//初始化每个点的In
		for(int i=1;i<=NE;++i)
			if(edge[i].lth<In[edge[i].to]&&edge[i].to!=edge[i].frm)
				In[edge[i].to]=edge[i].lth,pre[edge[i].to]=edge[i].frm;

//判有没有独立节点		
		for(int i=0;i<NV;++i){
			if(i==root)	continue;
			if(In[i]==INF)	return -1;
		}
		
//判环,缩环 
		cnt_col=0;
		memset(vis,-1,sizeof(vis));
		memset(col,-1,sizeof(col));
		In[root]=0;
		for(int i=0;i<NV;++i){
			ans+=In[i];
			int now=i;
			while(vis[now]!=i&&col[now]==-1&&now!=root){
				vis[now]=i;
				now=pre[now];
			}
			if(now!=root&&!(~col[now])){
				for(int tmp=pre[now];tmp!=now;tmp=pre[tmp])	col[tmp]=cnt_col;
				col[now]=cnt_col++;
			}
		}

//如果没有环就表示,树建好啦 
		if(!cnt_col)	return ans;
//给节点标新序号		
        for(int i=0;i<NV;i++)
            if(!(~col[i]))	col[i]=cnt_col++;
//按照 入边长度-=In[i] 的规则更新每个新节点之间的距离		
		for(int i=1;i<=NE;++i){
			int tt=edge[i].to;
			edge[i].frm=col[edge[i].frm];
			edge[i].to=col[edge[i].to];
			if(edge[i].frm!=edge[i].to)
				edge[i].lth-=In[tt];
		}
		NV=cnt_col;
		root=col[root];
	}
}

int main(){
	memset(fst,0,sizeof(fst));
	memset(edge,0,sizeof(edge));
	num[0]=0;
	
	while(scanf("%d%d",&n,&m)!=EOF&&(n||m)){
		for(int i=1;i<=n;++i)	scanf("%d",&aim[i]),++aim[i],num[i+1]=num[i]+aim[i];
		init();
		printf("%d\n",zhuliu(num[n+1],cnt_edge,num[n+1]+1));
	}
	return 0;
} 




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值