1. 程式人生 > >bzoj 4349: 最小樹形圖 朱-劉演算法

bzoj 4349: 最小樹形圖 朱-劉演算法

       最裸的最小樹形圖(←現在才學的弱渣)。

       顯然只需要打一下一個堡壘,然後剩下的可以最後用最小的代價再打。

       然後只要把圖建出來跑一下朱-劉演算法即可。

       簡單講一下朱-劉演算法吧(思想還是很簡單的),下面只考慮圖連通的情況:

       首先為每一條邊定一條邊權最小的入邊,並將所有這些入邊的和累加入答案。那麼如果沒有環,顯然現在所有的入邊就構成了最小樹形圖;

       否則,將構成的環縮成一個點,注意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