题意:求图的最小生成树,不过其中一个点在最小生成树中的度要小于等于k。
分析:先将其它点形成森林,每棵树都是最小生成树。然后从那个点(不妨设是0号结点)连条边到最小生成树中。这样一来整体就形成了一棵树,但不一定是最小生成树。
现在构造最小生成树有个贪心的方法,具体证明见黑书302页。不妨设,现在0号结点还剩度m,那么我们如果加一条0至其它一点的边,必定会存在环,此时肯定要去掉一条环上最大的一条边,至于连接0和哪个结点的边,就要枚举了,判断连哪个结点总权值减小的最大,如此进行m次即可。不过,枚举+找最长的边,时间复杂度为O(n^2)了,最多k次,总的是O(k*n^2)。优化是每次加入0至某个点的一条边时都更新从这条边能到达的其它点的路径的最大值。然后枚举时只要直接判断就行,找到加的那点后,加边时再更新其能到的点的最大值。这两个操作是并行的,时间复杂度为O(n),成功的将时间复杂度降为O(k*n)
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxint=0x3fffffff;
struct point
{
int u,v,maxw;
}p[110];
char s[110][110];
int mind,sum,kk,num,g[110][110],d[110],pre[110];
bool flag[110],link[110][110];
void Prim(int t)//与t在一个连通分量的点建最小生成树
{
flag[t]=true;
int i,j;
memset(pre,-1,sizeof(pre));
for(i=1;i<num;i++)
{
pre[i]=t;
d[i]=g[t][i];
}
while(1)
{
for(mind=maxint,j=-1,i=1;i<num;i++)
if(!flag[i]&&d[i]!=-1&&d[i]<mind)
{
mind=d[i];
j=i;
}
if(j==-1) break;
flag[j]=true;
if(g[0][j]!=-1&&(g[0][kk]==-1||g[0][kk]>g[0][j]))
kk=j;//找与0最近的点
link[pre[j]][j]=link[j][pre[j]]=true;
sum+=d[j];//加上总的生成树的权值
for(i=1;i<num;i++)
if(!flag[i]&&g[i][j]!=-1&&(d[i]==-1||d[i]>g[j][i]))
{
pre[i]=j;
d[i]=g[j][i];
}
}
}
void DFS(int t,int pt,int u,int v)
{
int i;
for(i=1;i<num;i++)
if(pt!=i&&link[t][i])
{
if(pt==-1||g[t][i]>=g[u][v])//(t,i)比(u,v)大
{
p[i].maxw=g[t][i];
p[i].u=t;
p[i].v=i;
DFS(i,t,t,i);
}
else
{
p[i].maxw=g[u][v];
p[i].u=u;
p[i].v=v;
DFS(i,t,u,v);
}
}
}
int main()
{
int n,i,j,k,w,ii,jj;
char s1[110],s2[110];
while(scanf("%d",&n)!=EOF)
{
num=1;
strcpy(s[0],"Park");//公园为0
memset(g,-1,sizeof(g));
for(i=1;i<=n;i++)//根据输入构图
{
scanf("%s%s%d",s1,s2,&w);
for(j=0;j<num;j++)
if(strcmp(s[j],s1)==0)
break;
ii=j;
if(ii==num) strcpy(s[num++],s1);
for(j=0;j<num;j++)
if(strcmp(s[j],s2)==0)
break;
jj=j;
if(jj==num) strcpy(s[num++],s2);
if(g[ii][jj]==-1||w<g[ii][jj])
g[ii][jj]=g[jj][ii]=w;
}
scanf("%d",&k);
memset(flag,false,sizeof(flag));
memset(link,false,sizeof(link));
sum=0;
for(i=1;i<num;i++)//将除0之外的建最小生成树并记录最长的一条边,并且每个连通分量加一条与0相连的边
if(!flag[i])
{
k--;
kk=i;
Prim(i);
sum+=g[0][kk];
link[0][kk]=link[kk][0]=true;
DFS(kk,-1,-1,-1);//加入(0,kk)一条边,更新每个点到0的路径中最长的边
}
while(k)
{
k--;
for(j=0,i=1;i<num;i++)//枚举连的点
{
if(link[0][i]||g[0][i]==-1) continue;//与0不连接,或者前面已经连接了
if(j<p[i].maxw-g[0][i])//找最长的边
{
ii=i;
j=p[i].maxw-g[0][i];
}
}
if(j==0) continue;
sum-=j;//边权和减小
link[p[ii].u][p[ii].v]=link[p[ii].v][p[ii].u]=false;//不连接了
link[0][ii]=link[ii][0]=true;//连接
DFS(ii,0,-1,-1);//更新最长的边
}
printf("Total miles driven: %d\n",sum);
}
return 0;
}