题解最小度限制生成树 ,详解减:
http://wenku.baidu.com/view/70ef0e00eff9aef8941e06db.html
要求最小 k 度生成树,我们可以按照下面的步骤来做:
设有度限制的点为 V0 ,V0称为根节点
1,把所有与 V0 相连的边删去,图会分成多个子图(假设为 m 个,显然的,如果 m > k,那么问题无解),让他们分别求最小生成树;然后用最小的代价将 m 个最小生成树和 V0 连起来,那我们就得到了一棵关于 V0 的最小 m 度生成树。
2,在 m 度生成树中找一个点和 V0 相连(设这条边的权值为 a),会生成一个环,为了满足最小生成树的要求,我们必须删掉一条边(设这条边的权值为 b),以使总权值尽量小,那么就要求 a 尽量的小,b 尽量的大。
完成一次 2 的操作后得到的是 m+1 度最小生成树,以此类推,直到得到最小 k 度生成树。
具体参见 汪汀2004年的国家集训队论文,讲的很详细。

#include<stdio.h> #include<iostream> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<set> #include<map> #define Min(a,b) a>b?b:a #define Max(a,b) a>b?a:b #define CL(a,num) memset(a,num,sizeof(a)); #define inf 9999999 #define maxn 30 using namespace std; struct node { int u; int cost; node(int x,int y):u(x),cost(y){} friend bool operator < (node a,node b) { return a.cost>b.cost; } }; map<string ,int >map1; priority_queue<node>que; int dis[maxn],pre[maxn],clo[maxn],near[maxn],mat[maxn][maxn]; int max_side[maxn]; int res,k,n; int h; int prim(int x,int id)//运用优先队列 求最小生成树 { dis[x]=0; while(!que.empty())que.pop(); que.push(node(x,0)); int res=0,i; while(!que.empty()) { node a=que.top();que.pop(); int u=a.u; int cost=a.cost; if(!clo[u]) { clo[u]=id; res+=dis[u]; for(i=1;i<n;i++) { if(!clo[i]&&mat[u][i]!=0&&mat[u][i]<dis[i]) { pre[i]=u; dis[i]=mat[u][i]; que.push(node(i,dis[i])); } } } } return res; } void update(int cur,int last,int maxside)// dfs 更新最大边 { 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))//pre[]将其他边 和生成树的边分离开 update(i,cur,max_side[cur]); } } void solve() { int i; for(i=0;i<n;i++) { dis[i]=inf; clo[i]=pre[i]=near[i]=0; } int res=0; int cnt=1; clo[0]=1; h=0; for(i=1;i<n;i++)//将 0 点 叙其他点断开 求生成树 { if(!clo[i])res+=prim(i,cnt++); } for(i=1;i<n;i++)//near[i],记录每个生成树距离 0 点,最近的点 { int id=clo[i]; if(mat[0][i]!=0&&(!near[id]||mat[0][near[id]]>mat[0][i])) near[id]=i; } for(i=1;i<cnt;i++)////把m个生成树上和根节点相连的边加入res,得到关于Park的最小m度生成树 { res+=mat[0][near[i]]; mat[0][near[i]]=mat[near[i]][0]=0; update(near[i],0,0); } k=k-cnt+1; /* 添删操作:将根节点和生成树中一个点相连,会产生一个环,将这个环上(除刚添的那条边外)权值最大 的边删去.由于每次操作都会给总权值带来影响 d=max_side[tmp]-mat[0][tmp],我们需要得到最小生 成树,所以我们就要求 d 尽量大 */ while(k--) { int tmp=0; for(i=1;i<n;i++)//找 d最大的点 极 max_side[i]-mat[0][i],.最大 { 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,i; string a,b; int d; //freopen("int.txt","r",stdin); while(scanf("%d",&m)!=EOF) { map1.clear(); map1["Park"]=0; memset(mat,0,sizeof(mat)); n=1; for(i=0;i<m;i++) { cin>>a>>b>>d; if(!map1.count(a))map1[a]=n++; if(!map1.count(b))map1[b]=n++; int x=map1[a]; int y=map1[b]; if(!mat[x][y]||mat[x][y]>d) mat[x][y]=mat[y][x]=d; } scanf("%d",&k); solve(); } }