POJ1639 Picnic Planning(度限制生成树)

来源:互联网 发布:淘宝店铺装修图 编辑:程序博客网 时间:2024/06/10 14:36

黑书上的例题,所以题意就不啰嗦了,具体模型是求一个无向图的最小生成树,其中有一个点的度有限制(假设为 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;}


原创粉丝点击