Picnic Planning poj 1639 k度限制生成树

传送门:poj1639

题意就很晦涩,看了半天才看懂。。我就不解释了,没看懂的再去看看吧。。

重点的重点是本题用到的算法:K度限制最小生成树

即求一个图的最小生成树,且给定点v的度数必须为k。

这也是我第一次接触这个名词,百度了一些资料罗列在下面:

国家集训队2004论文集_汪汀:http://wenku.baidu.com/view/8abefb175f0e7cd1842536aa.html

大牛们的博客:http://www.cnblogs.com/Missa/archive/2013/04/07/3005880.html

http://www.cnblogs.com/jackge/archive/2013/05/12/3073669.html

http://blog.csdn.net/qq_33951440/article/details/53115853

这个算法给我的感觉就是特别容易懂思路,但是并不好实现,尤其是其中的dfs更是神来之笔(弱搞了好久才明白)。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
int k,n,cnt;
int g[25][25];
int col[25];//记录每颗子树的序号 
int dis[25];//prim中距离数组 
int pre[25];//记录每个点的前驱 
int max_side[25];//记录每个点到主根节点的路径中的最长边
int min_side[25];//记录每颗子树中到主根节点的最短边对应的点 
struct node{
	int v,w;
	bool friend operator<(node a,node b)
	{
	return a.w>b.w;
	}
};
int prim(int t,int i)
{
	priority_queue<node> q;
	while(!q.empty())
	q.pop();
	q.push((node){t,0});
	dis[t]=0;
	int sum=0;
	while(!q.empty())
	{
		node a=q.top();
		q.pop();
		if(!col[a.v])
		{
			col[a.v]=i;
			sum+=dis[a.v];
			for(int i=1;i<cnt;i++)
			{
				if(!col[i]&&g[a.v][i]&&g[a.v][i]<dis[i])
				{
					pre[i]=a.v;
					dis[i]=g[a.v][i];
					q.push((node){i,g[a.v][i]});
				}
			}
		} 
	}
	return sum;
}
int dfs(int cur,int fa,int maxw)//求每个点到主根的路径中的最长边 
{
	max_side[cur]=max(maxw,g[cur][fa]);
	for(int i=1;i<cnt;i++)
	if(g[cur][i]&&i!=fa&&(pre[i]==cur||pre[cur]==i)) 
	dfs(i,cur,max_side[cur]);
}
void solve()
{
	for(int i=0;i<cnt;i++)
	{
		dis[i]=inf;
		pre[i]=col[i]=min_side[i]=0; 
	}
	int ans=0,t=1;
	for(int i=1;i<cnt;i++)
	{
		if(!col[i])
		ans+=prim(i,t++);
	}
	for(int i=1;i<cnt;i++)
	{
		int j=col[i];
		if(g[0][i]&&(!min_side[j]||g[0][i]<g[0][min_side[j]]))
		min_side[j]=i; 
	}
	for(int i=1;i<t;i++)
	{
		ans+=g[0][min_side[i]];
		g[0][min_side[i]]=g[min_side[i]][0]=0;//删除这条边(以后不再允许使用) 
		dfs(min_side[i],0,0);
	}
	k=k-t+1;
	while(k--)
	{
		int tmp=0;
		for(int i=1;i<cnt;i++)
		{
			if(g[0][i]&&(!tmp||max_side[i]-g[0][i]>max_side[tmp]-g[0][tmp]))
			tmp=i;
		}
		if(max_side[tmp]-g[0][tmp]<=0)
		break;
		ans-=(max_side[tmp]-g[0][tmp]);
		g[0][tmp]=g[tmp][0]=0;
		int temp=0;
		for(int i=tmp;pre[i]!=0;i=pre[i])//找到形成的环内最长的边对应的顶点 
		{
			if(g[i][pre[i]]&&(!temp||g[temp][pre[temp]]<g[i][pre[i]]))
			temp=i;
		}
		pre[temp]=0;//将最长边的父节点改为0,个人认为是要删除这条边,但是又不能直接将g数组中的值归零 
		dfs(tmp,0,0);
	}
	printf("Total miles driven: %d\n",ans); 
}
int main()
{
	int w;
	char a[15],b[15];
	map<string,int>mp;
	while(~scanf("%d",&n))
	{
		mp.clear();
		memset(g,0,sizeof(g)); 
		mp["Park"]=0;
		cnt=1;
	while(n--)
	{
		scanf("%s%s%d",a,b,&w);
		if(!mp.count(a))
		mp[a]=cnt++;
		if(!mp.count(b))
		mp[b]=cnt++;
		int i=mp[a],j=mp[b];
		if(!g[i][j]||g[i][j]>w)
		g[i][j]=g[j][i]=w;
	}
	scanf("%d",&k);
	solve();
	}
	
return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值