bzoj4349 最小树形图 朱-刘算法

       最裸的最小树形图(←现在才学的弱渣)。

       显然只需要打一下一个堡垒,然后剩下的可以最后用最小的代价再打。

       然后只要把图建出来跑一下朱-刘算法即可。

       简单讲一下朱-刘算法吧(思想还是很简单的),下面只考虑图连通的情况:

       首先为每一条边定一条边权最小的入边,并将所有这些入边的和累加入答案。那么如果没有环,显然现在所有的入边就构成了最小树形图;

       否则,将构成的环缩成一个点,注意n个点n条边只能构成一个最简单的环(因此不需要跑Tarjan强联通分量),那么就找到上面的一个点,走一圈重标号即可。

       重复上述过程,直到没有环(显然只剩下一个点的时候也是没有环的)。

       但是这样得到的答案显然是不对的。注意一个环中的某一个点x,再一次找x的最小入边的时候,根据流程要把x的最小入边的边权累加入答案,但是x原来的最小入边也累加入答案,显然这样是会重复的;那么不妨在缩点的时候让所有x的入边都减去当前x的最小入边。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 20005
using namespace std;

int n,m,cnt,tot,pre[N],num[N],vis[N],id[N],p[N];
double rch[N],c[N];
struct edg{ int x,y; double z; }e[N];
double solve(int rt){
	int i,j,k; double tmp=0;
	while (1){
		for (i=1; i<=n; i++) rch[i]=1e100;
		for (i=1; i<=m; i++)
			if (e[i].x!=e[i].y && e[i].z<rch[e[i].y]){
				pre[e[i].y]=e[i].x; rch[e[i].y]=e[i].z;
			}
		rch[rt]=0;
		for (i=1; i<=n; i++)
			if (rch[i]==1e100) return -1;
		for (i=1; i<=n; i++) vis[i]=id[i]=0;
		cnt=0;
		for (i=1; i<=n; i++){
			tmp+=rch[i];
			for (k=i; vis[k]!=i && !id[k] && k!=rt; k=pre[k]) vis[k]=i;
			if (!id[k] && k!=rt){
				id[k]=++cnt;
				for (j=pre[k]; j!=k; j=pre[j]) id[j]=cnt;
			}
		}
		if (!cnt) return tmp;
		for (i=1; i<=n; i++) if (!id[i]) id[i]=++cnt;
		for (i=1; i<=m; i++){
			double t=rch[e[i].y];
			e[i].x=id[e[i].x]; e[i].y=id[e[i].y];
			if (e[i].x!=e[i].y) e[i].z-=t;
		}
		n=cnt; rt=id[rt];
	}
}
int main(){
	scanf("%d",&n); int i,x,y; double t;
	for (i=1; i<=n; i++){
		scanf("%lf%d",&t,&x);
		if (x){
			p[i]=++m; e[m].y=m;
			num[m]=x; c[m]=e[m].z=t;
		}
	}
	n=m+1; tot=m;
	for (i=1; i<n; i++) e[i].x=n;
	scanf("%d",&cnt);
	while (cnt--){
		scanf("%d%d%lf",&x,&y,&t);
		if (p[x] && p[y]){
			e[++m].x=p[x]; y=e[m].y=p[y]; 
			e[m].z=t; c[y]=min(c[y],t);
		}
	}
	double ans=solve(n);
	for (i=1; i<=tot; i++)
		if (num[i]>1) ans+=(num[i]-1)*c[i];
	printf("%.2f\n",ans);
	return 0;
}


by lych

2016.3.31

你可能感兴趣的:(最小树形图,朱-刘算法)