现在的位置: 首页 > 综合 > 正文

POJ1639 Picnic Planning(度限制生成树)

2013年09月09日 ⁄ 综合 ⁄ 共 2769字 ⁄ 字号 评论关闭

黑书上的例题,所以题意就不啰嗦了,具体模型是求一个无向图的最小生成树,其中有一个点的度有限制(假设为 k)。

要求最小 k 度生成树,我们可以按照下面的步骤来做:

设有度限制的点为 V0 ,V0称为根节点

1,把所有与 V0 相连的边删去,图会分成多个子图(假设为 m 个,显然的,如果 m > k,那么问题无解),让他们分别求最小生成树;然后用最小的代价将 m 个最小生成树和 V0 连起来,那我们就得到了一棵关于 V0 的最小 m 度生成树。

2,在 m 度生成树中找一个点和 V0 相连(设这条边的权值为 a),会生成一个环,为了满足最小生成树的要求,我们必须删掉一条边(设这条边的权值为 b),以使总权值尽量小,那么就要求 a 尽量的小,b 尽量的大。

完成一次 2 的操作后得到的是 m+1 度最小生成树,以此类推,直到得到最小 k 度生成树。

具体参见
汪汀2004年的国家集训队论文
,讲的很详细。

PS:这道题并不是要求 k 度的最小生成树,而是要求根节点的度在不超过 k 值的情况下,该图的最小生成树。也就是说,不一定要求到 k 度生成树,只要图的总权值不能继续减小我们就可以停下来了。

代码是“拿来”的,因为用map处理数据,所以代码量小了一点,而且方便的多了,STL是真的很强大,orz……

#pragma warning (disable : 4786)//屏蔽VC中的一些警告
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<queue>
#include<map>
#include<climits>
using namespace std;

const int N = 21;

struct node{
	int v,cost;//点 v ,dist[v]
	node(){}
	node(int vv,int ww):v(vv),cost(ww) {}
};

bool operator < (const node &l,const node &r){
	return l.cost > r.cost;
}

priority_queue<node> q;
map<string,int> Map;
int mat[N][N],dist[N],clo[N],pre[N],fst[N],max_side[N];
int n,K;

int prim(int s,int id){
	while(!q.empty())q.pop();
	dist[s]=0;
	q.push(node(s,0));
	int res=0;
	while(!q.empty()){
		node cur=q.top();
		q.pop();
		int u=cur.v;
		if(!clo[u]){
			clo[u]=id;
			res+=dist[u];
			for(int i=1;i<n;i++){
				if(!clo[i] && mat[u][i]!=0 && mat[u][i]<dist[i]){//满足松弛条件
					pre[i]=u; dist[i]=mat[u][i];
					q.push(node(i,dist[i]));
				}
			}
		}
	}
	return res;
}

void update(int cur,int last,int maxside){//也是一个dfs过程,直到搜回到起点,同时完成了max_side[]更新
	max_side[cur]=maxside>mat[cur][last]?maxside:mat[cur][last];
	for(int i=1;i<n;i++){
		if(i!=last && mat[cur][i]!=0 && (pre[cur]==i || pre[i]==cur))
			update(i,cur,max_side[cur]);
	}
}

void solve(){
	int i,res,cnt;
	for(i=0;i<n;i++){
		dist[i]=INT_MAX;
		clo[i]=pre[i]=fst[i]=0;
	}
	res=0; cnt=1;//除去根节点后,图中的连通子图个数,即最小生成树个数
	for(i=1;i<n;i++){
		if(!clo[i]) res+=prim(i,cnt++);
	}
	for(i=1;i<n;i++){//找到每个生成树和 Park 最近的点使之和 Park 相连
		int id=clo[i];
		if(mat[0][i]!=0 && (!fst[id] || mat[0][i]<mat[0][fst[id]]) )
			fst[id]=i;
	}
	for(i=1;i<cnt;i++){//把m个生成树上和根节点相连的边加入res,得到关于Park的最小m度生成树
		res+=mat[0][fst[i]];
		mat[0][fst[i]]=mat[fst[i]][0]=0;//之所以用邻接阵就是因为删除边很方便
		update(fst[i],0,0);
	}
	/*
	添删操作:将根节点和生成树中一个点相连,会产生一个环,将这个环上(除刚添的那条边外)权值最大
	的边删去.由于每次操作都会给总权值带来影响 d=max_side[tmp]-mat[0][tmp],我们需要得到最小生
	成树,所以我们就要求 d 尽量大
	*/
	K=K-cnt+1;//接下来重复操作,直到度数满足条件
	while(K--){
		int tmp=0;
		for(i=1;i<n;i++){//找 d 值最大的点(就是说完成添删操作后可以使总边权减小的值最大)
			if(mat[0][i]!=0 && (tmp==0 || max_side[tmp]-mat[0][tmp]<max_side[i]-mat[0][i]))
				tmp=i;
		}
		if(max_side[tmp]<=mat[0][tmp])break;//总权值无法再减小
		res-=max_side[tmp]-mat[0][tmp];
		mat[0][tmp]=mat[tmp][0]=0;
		int p=0;
		for(i=tmp;pre[i]!=0;i=pre[i]){
			if(p==0 || mat[p][ pre[p] ]<mat[i][ pre[i] ]) p=i;
		}
		pre[p]=0;
		update(tmp,0,0);
	}
	printf("Total miles driven: %d\n",res);
}

int main()
{
	int m,x;
	string ch1,ch2;
	while(~scanf("%d",&m))
	{
		Map["Park"]=0;
		n=1;
		memset(mat,0,sizeof(mat));
		while(m--){
			cin>>ch1>>ch2>>x;
			if(!Map.count(ch1)) Map[ch1]=n++;
			if(!Map.count(ch2)) Map[ch2]=n++;
			int u=Map[ch1];
			int v=Map[ch2];
			if(!mat[u][v] || x<mat[u][v])
				mat[u][v]=mat[v][u]=x;
		}
		scanf("%d",&K);
		solve();
	}
	return 0;
}

抱歉!评论已关闭.