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