pku1639最小度限制生成树

        题意:求图的最小生成树,不过其中一个点在最小生成树中的度要小于等于k。

       分析:先将其它点形成森林,每棵树都是最小生成树。然后从那个点(不妨设是0号结点)连条边到最小生成树中。这样一来整体就形成了一棵树,但不一定是最小生成树。

       现在构造最小生成树有个贪心的方法,具体证明见黑书302页。不妨设,现在0号结点还剩度m,那么我们如果加一条0至其它一点的边,必定会存在环,此时肯定要去掉一条环上最大的一条边,至于连接0和哪个结点的边,就要枚举了,判断连哪个结点总权值减小的最大,如此进行m次即可。不过,枚举+找最长的边,时间复杂度为O(n^2)了,最多k次,总的是O(k*n^2)。优化是每次加入0至某个点的一条边时都更新从这条边能到达的其它点的路径的最大值。然后枚举时只要直接判断就行,找到加的那点后,加边时再更新其能到的点的最大值。这两个操作是并行的,时间复杂度为O(n),成功的将时间复杂度降为O(k*n)

#include<stdio.h>
#include<iostream>
using namespace std;

const int maxint=0x3fffffff;
struct point
{
	int u,v,maxw;
}p[110];

char s[110][110];
int mind,sum,kk,num,g[110][110],d[110],pre[110];
bool flag[110],link[110][110];
void Prim(int t)//与t在一个连通分量的点建最小生成树
{
	flag[t]=true;
	int i,j;

	memset(pre,-1,sizeof(pre));
	for(i=1;i<num;i++)
	{
		pre[i]=t;
		d[i]=g[t][i];
	}
	while(1)
	{
		for(mind=maxint,j=-1,i=1;i<num;i++)
			if(!flag[i]&&d[i]!=-1&&d[i]<mind)
			{
				mind=d[i];
				j=i;
			}
		if(j==-1) break;
		flag[j]=true;
		if(g[0][j]!=-1&&(g[0][kk]==-1||g[0][kk]>g[0][j]))
			kk=j;//找与0最近的点
		link[pre[j]][j]=link[j][pre[j]]=true;
		sum+=d[j];//加上总的生成树的权值
		for(i=1;i<num;i++)
			if(!flag[i]&&g[i][j]!=-1&&(d[i]==-1||d[i]>g[j][i]))
			{
				pre[i]=j;
				d[i]=g[j][i];
			}
	}
}
void DFS(int t,int pt,int u,int v)
{
	int i;
	for(i=1;i<num;i++)
		if(pt!=i&&link[t][i])
		{
			if(pt==-1||g[t][i]>=g[u][v])//(t,i)比(u,v)大
			{
				p[i].maxw=g[t][i];
				p[i].u=t;
				p[i].v=i;
				DFS(i,t,t,i);
			}
			else
			{
				p[i].maxw=g[u][v];
				p[i].u=u;
				p[i].v=v;
				DFS(i,t,u,v);
			}
		}
}
int main()
{
	int n,i,j,k,w,ii,jj;
	char s1[110],s2[110];

	while(scanf("%d",&n)!=EOF)
	{
		num=1;
		strcpy(s[0],"Park");//公园为0
		memset(g,-1,sizeof(g));
		for(i=1;i<=n;i++)//根据输入构图
		{
			scanf("%s%s%d",s1,s2,&w);
			for(j=0;j<num;j++)
				if(strcmp(s[j],s1)==0)
					break;
			ii=j;
			if(ii==num) strcpy(s[num++],s1);
			for(j=0;j<num;j++)
				if(strcmp(s[j],s2)==0)
					break;
			jj=j;
			if(jj==num) strcpy(s[num++],s2);
			if(g[ii][jj]==-1||w<g[ii][jj])
				g[ii][jj]=g[jj][ii]=w;
		}

		scanf("%d",&k);
		memset(flag,false,sizeof(flag));
		memset(link,false,sizeof(link));
		sum=0;
		for(i=1;i<num;i++)//将除0之外的建最小生成树并记录最长的一条边,并且每个连通分量加一条与0相连的边
			if(!flag[i])
			{
				k--;
				kk=i;
				Prim(i);
				sum+=g[0][kk];
				link[0][kk]=link[kk][0]=true;
				DFS(kk,-1,-1,-1);//加入(0,kk)一条边,更新每个点到0的路径中最长的边
			}

		while(k)
		{
			k--;
			for(j=0,i=1;i<num;i++)//枚举连的点
			{
				if(link[0][i]||g[0][i]==-1) continue;//与0不连接,或者前面已经连接了
				if(j<p[i].maxw-g[0][i])//找最长的边
				{
					ii=i;
					j=p[i].maxw-g[0][i];
				}
			}
			if(j==0) continue;
			sum-=j;//边权和减小
			link[p[ii].u][p[ii].v]=link[p[ii].v][p[ii].u]=false;//不连接了
			link[0][ii]=link[ii][0]=true;//连接
			DFS(ii,0,-1,-1);//更新最长的边
		}
		printf("Total miles driven: %d\n",sum);
	}
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值