传送门:poj1639
题意就很晦涩,看了半天才看懂。。我就不解释了,没看懂的再去看看吧。。
重点的重点是本题用到的算法:K度限制最小生成树
即求一个图的最小生成树,且给定点v的度数必须为k。
这也是我第一次接触这个名词,百度了一些资料罗列在下面:
国家集训队2004论文集_汪汀:http://wenku.baidu.com/view/8abefb175f0e7cd1842536aa.html
大牛们的博客:http://www.cnblogs.com/Missa/archive/2013/04/07/3005880.html
http://www.cnblogs.com/jackge/archive/2013/05/12/3073669.html
http://blog.csdn.net/qq_33951440/article/details/53115853
这个算法给我的感觉就是特别容易懂思路,但是并不好实现,尤其是其中的dfs更是神来之笔(弱搞了好久才明白)。。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
int k,n,cnt;
int g[25][25];
int col[25];//记录每颗子树的序号
int dis[25];//prim中距离数组
int pre[25];//记录每个点的前驱
int max_side[25];//记录每个点到主根节点的路径中的最长边
int min_side[25];//记录每颗子树中到主根节点的最短边对应的点
struct node{
int v,w;
bool friend operator<(node a,node b)
{
return a.w>b.w;
}
};
int prim(int t,int i)
{
priority_queue<node> q;
while(!q.empty())
q.pop();
q.push((node){t,0});
dis[t]=0;
int sum=0;
while(!q.empty())
{
node a=q.top();
q.pop();
if(!col[a.v])
{
col[a.v]=i;
sum+=dis[a.v];
for(int i=1;i<cnt;i++)
{
if(!col[i]&&g[a.v][i]&&g[a.v][i]<dis[i])
{
pre[i]=a.v;
dis[i]=g[a.v][i];
q.push((node){i,g[a.v][i]});
}
}
}
}
return sum;
}
int dfs(int cur,int fa,int maxw)//求每个点到主根的路径中的最长边
{
max_side[cur]=max(maxw,g[cur][fa]);
for(int i=1;i<cnt;i++)
if(g[cur][i]&&i!=fa&&(pre[i]==cur||pre[cur]==i))
dfs(i,cur,max_side[cur]);
}
void solve()
{
for(int i=0;i<cnt;i++)
{
dis[i]=inf;
pre[i]=col[i]=min_side[i]=0;
}
int ans=0,t=1;
for(int i=1;i<cnt;i++)
{
if(!col[i])
ans+=prim(i,t++);
}
for(int i=1;i<cnt;i++)
{
int j=col[i];
if(g[0][i]&&(!min_side[j]||g[0][i]<g[0][min_side[j]]))
min_side[j]=i;
}
for(int i=1;i<t;i++)
{
ans+=g[0][min_side[i]];
g[0][min_side[i]]=g[min_side[i]][0]=0;//删除这条边(以后不再允许使用)
dfs(min_side[i],0,0);
}
k=k-t+1;
while(k--)
{
int tmp=0;
for(int i=1;i<cnt;i++)
{
if(g[0][i]&&(!tmp||max_side[i]-g[0][i]>max_side[tmp]-g[0][tmp]))
tmp=i;
}
if(max_side[tmp]-g[0][tmp]<=0)
break;
ans-=(max_side[tmp]-g[0][tmp]);
g[0][tmp]=g[tmp][0]=0;
int temp=0;
for(int i=tmp;pre[i]!=0;i=pre[i])//找到形成的环内最长的边对应的顶点
{
if(g[i][pre[i]]&&(!temp||g[temp][pre[temp]]<g[i][pre[i]]))
temp=i;
}
pre[temp]=0;//将最长边的父节点改为0,个人认为是要删除这条边,但是又不能直接将g数组中的值归零
dfs(tmp,0,0);
}
printf("Total miles driven: %d\n",ans);
}
int main()
{
int w;
char a[15],b[15];
map<string,int>mp;
while(~scanf("%d",&n))
{
mp.clear();
memset(g,0,sizeof(g));
mp["Park"]=0;
cnt=1;
while(n--)
{
scanf("%s%s%d",a,b,&w);
if(!mp.count(a))
mp[a]=cnt++;
if(!mp.count(b))
mp[b]=cnt++;
int i=mp[a],j=mp[b];
if(!g[i][j]||g[i][j]>w)
g[i][j]=g[j][i]=w;
}
scanf("%d",&k);
solve();
}
return 0;
}