思路:
对于一个限制Vo点K度生成树的题来说,首先把他的Vo点所有的边都删除,对于其他的点进行初步prim建立多个最小生成树,并且存放最大边值,之后再对于这些最小生成树进行向Vo点连边。
国家集训队论文,讲得很好http://wenku.baidu.com/view/8abefb175f0e7cd1842536aa.html
代码是copy的。注释是自己的,感觉这个模板每一步都很有意思。如果看完论文没看明白可以试着读读代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<climits>
#include<queue>
using namespace std;
const int N=30;
struct node
{
int v,cap;
bool friend operator < ( node a, node b)
{
return a.cap>b.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 cur ,int id) //队列优化的prim算法
{
priority_queue<node> q;//从小到大的优先队列
while(!q.empty())
q.pop();
dis[cur]=0;
node t;
t.cap=0,t.v=cur; //代码从0点开始遍历
q.push(t);
int ans=0;
while(!q.empty())
{
node point=q.top();
q.pop();
int u=point.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;//在point.v( u ) 起点的基础上进行了松弛,被松弛的边的父亲进行修改
dis[i]=g[u][i];//修改当前树到达i点的最小权值
node temp;
temp.cap=dis[i];
temp.v=i;
q.push(temp);
}
}
}
}
return ans;
}
void update(int cur,int last,int maxside) //这一步的DFS是一个关键,因为之前我们已经知道了当前树如果与总根0相连,可以花费的最小权值,以及花费最小全知道的那个点fst[i]
{ //因此,这个函数的根本意义是在于把当前树所有的点与fst[i]相连成为最小生成树的花费更新出来
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))// i!=last 防止来回遍历, g[cur][i] 对于cur 与i 这个点是否有还未使用的边,pre[cur]==i||pre[i]==cur 是否cur i存在直接相连的关系
{
update(i,cur,max_side[cur]);
}
}
void solve() //这一个函数是解决限制度生成树的关键
{
for(int i=0;i<n;i++)
{
dis[i]=INT_MAX;
clo[i]=pre[i]=fst[i]=0; //clo属于第几棵树,加入的标记, pre父节点,fst 在i所在的树中最大边fst[i]
}
int res=0,cnt=0;
for(int i=1;i<n;i++) //对于未被访问过的点进行prim初步构造
{
if(!clo[i])
res+=Prim(i,++cnt);
}
for(int i=1;i<n;i++) //进行最大边的更改
{
int id=clo[i]; //第N棵树
if(g[0][i]!=0&&(!fst[id]||g[0][i]<g[0][fst[id]]))
fst[id]=i; //当前树最小的与0连的权值的i节点
}
for(int i=1;i<=cnt;i++) //加入所有边与 根节点(0)所连时花费的权值
{
res+=g[0][fst[i]];
g[0][fst[i]]=g[fst[i]][0]=0;
update(fst[i],0,0); //每次都重新更新一次树i的fst数组
}
k=k-cnt; //接下来重复操作,直到度数满足条件
while(k--) //每次多加一条边,看看是否有一个在树中的点,直接到0,比原来在自己的生成树中的值还小(因为我们当初把所有和0连通的边都删除了,这次依次回代)
{
int tmp=0;
for(int i=1;i<n;i++) //找一个差值最大的
{
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(int 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(){
char s1[20],s2[20];
int cap;//以下是一个prim算法的基础上进行修改变为限制度最小生成树的问题
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;
}