Time Limit: 5000MS | Memory Limit: 10000K | |
Total Submissions: 8340 | Accepted: 2946 |
Description
Input
Output
Sample Input
10 Alphonzo Bernardo 32 Alphonzo Park 57 Alphonzo Eduardo 43 Bernardo Park 19 Bernardo Clemenzi 82 Clemenzi Park 65 Clemenzi Herb 90 Clemenzi Eduardo 109 Park Herb 24 Herb Eduardo 79 3
Sample Output
Total miles driven: 183
Source
黑书上的例题,所以题意就不啰嗦了,具体模型是求一个无向图的最小生成树,其中有一个点的度有限制(假设为 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……
这个解题报告也挺不错的:http://www.myexception.cn/program/797359.html
#include<iostream> #include<cstdio> #include<cstring> #include<map> #include<climits> #include<queue> using namespace std; const int N=30; struct node{ int v,cap; node(){} node(int _v,int _cap):v(_v),cap(_cap){} bool operator < (const node &a) const{ return a.cap<cap; } }; map<string,int> mp; int g[N][N],dis[N],clo[N],pre[N],fst[N],max_side[N]; int n,m,k; int Prim(int src,int id){ priority_queue<node> q; while(!q.empty()) q.pop(); dis[src]=0; q.push(node(src,0)); int ans=0; while(!q.empty()){ node cur=q.top(); q.pop(); int u=cur.v; if(!clo[u]){ clo[u]=id; ans+=dis[u]; for(int i=1;i<n;i++) if(!clo[i] && g[u][i]!=0 && dis[i]>g[u][i]){ //满足松弛条件 pre[i]=u; dis[i]=g[u][i]; q.push(node(i,dis[i])); } } } return ans; } void update(int cur,int last,int maxside){ //也是一个dfs过程,直到搜回到起点,同时完成了max_side[]更新 max_side[cur]=maxside>g[cur][last]?maxside:g[cur][last]; for(int i=1;i<n;i++) if(i!=last && g[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++){ dis[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(g[0][i]!=0 && (!fst[id] || g[0][i]<g[0][fst[id]])) fst[id]=i; } for(i=1;i<cnt;i++){ //把m个生成树上和根节点相连的边加入res,得到关于Park的最小m度生成树 res+=g[0][fst[i]]; g[0][fst[i]]=g[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(g[0][i]!=0 && (tmp==0 || max_side[tmp]-g[0][tmp]<max_side[i]-g[0][i])) tmp=i; if(max_side[tmp]<=g[0][tmp]) //总权值无法再减小 break; res=res-max_side[tmp]+g[0][tmp]; g[0][tmp]=g[tmp][0]=0; int p=0; for(i=tmp;pre[i]!=0;i=pre[i]) if(p==0 || g[p][pre[p]]<g[i][pre[i]]) p=i; pre[p]=0; update(tmp,0,0); } printf("Total miles driven: %d\n",res); } int main(){ //freopen("input.txt","r",stdin); char s1[20],s2[20]; int cap; while(~scanf("%d",&m)){ mp["Park"]=0; n=1; memset(g,0,sizeof(g)); while(m--){ scanf("%s %s %d",s1,s2,&cap); if(!mp.count(s1)) mp[s1]=n++; if(!mp.count(s2)) mp[s2]=n++; int u=mp[s1],v=mp[s2]; if(!g[u][v] || g[u][v]>cap) g[u][v]=g[v][u]=cap; } scanf("%d",&k); Solve(); } return 0; }