题目大意:
给出m条边,每条边有两个端点和一个权值;
求这个图在满足以下条件的情况下的最小生成树;
在所有点中,有一个特殊点Park,它在求得的最小生成树中的度必须小于等于某个值;
思路:
k度限制最小生成树,也就是某个顶点有度数限制的生成树。
对这道题来说是名字为Park的顶点有度数限制,最大为k,我们把这个顶点设为v0好了。
首先需要知道生成树有一个回路性质:设C为图G中的一个回路,边e是C上权值最大的边,
则图G的所有生成树均不包含e。
然后我们能给出求度限制最小生成树的几个步骤:
(1)先在图G中删掉顶点v0,然后求剩下的图的最小生成树,得到图G1,此时可能会有若干个生成树,
它们分别都是图中的一个连通分量。
(2)然后让v0与这些连通分量都分别连上一条边,该边为图G中v0与某个连通分量中的所有顶点
的边中最短的那条,得到图G2,此时可以得到图G中出现生成树时v0的最小度数ki。
(3)目前我们已经得到v0度数等于ki时的最小生成树,接下来要得到v0度数小于等于k的最小生成树,只要能找到求出v0度数等于ki+1的最小生成树的方法就可以了。这个方法需要用到上面的回路性质,首先因为G2是一棵树,所以如果我们在图G2中给v0和其他顶点连上一条边(即增加了v0的度数),则一定会出现一条回路,根据回路性质我们需要找到这条回路中权值最大的边(除了与v0相连的边),并把它删去,这样就得到了ki+1的最小生成树。
算法不难理解,实现起来会有些复杂,比较重要的一个优化是找到回路中权值最大的边时先用dp求出并保存v0到其他顶点的路径中权值最大的边,这样就只不用每次都遍历整个图了.
AC代码:
(麻烦在树上的点居然都是名字没有具体的数字代表,否则就好处理多了,所以需要根据名字来建图)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<map>
#include<vector>
#include<algorithm>
#define CLR(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn = 105;
const int inf = 1e9;
int n,m; // n表示边的数量,m表示限度值.
int cnt; //计算出来的节点值.
int pre[maxn];
bool flag[maxn][maxn]; //标记那些点之间是连通关系的.
int G[maxn][maxn]; //存入的初始化信息.
int ans;
map<string,int> Map;
struct node{ int u,v,w; } a[maxn*maxn];
node dp[maxn*maxn]; //dp[v]为路径v0—v上与v0无关联且权值最大的边
bool cmp(node a,node b){ return a.w<b.w; }
int Find(int x){ return pre[x]==x?x:pre[x]=Find(pre[x]); }
int get_sum(string s) //返回每个人对应的节点.
{
if(Map.find(s)==Map.end()) // 没有搜索到该键值,这里用了map的一个操作函数find.
{
Map[s]=++cnt;
}
return Map[s];
}
void init()
{
ans=0;
cnt=1;
Map["Park"]=1; //把那个特殊的点设置为1.
CLR(flag); //初始化.
memset(G,-1,sizeof(G)); //初始化.
scanf("%d",&n);
for(int i=1;i<maxn;i++) //初始化根节点.
pre[i]=i;
string s;
for(int i=1;i<=n;i++){ //对应建图.
cin >> s;
a[i].u=get_sum(s);
cin >> s;
a[i].v=get_sum(s);
cin >> a[i].w;
if(G[a[i].u][a[i].v] == -1)
G[a[i].u][a[i].v]=G[a[i].v][a[i].u]=a[i].w; //G中存在是建的那个对应点的边.
else //有重边的情况.
G[a[i].u][a[i].v]=G[a[i].v][a[i].u]=min(G[a[i].v][a[i].u],a[i].w);
}
scanf("%d",&m);
}
void kruskal()
{
for(int i=1;i<=n;i++){
if(a[i].u==1 || a[i].v==1 ) continue;
int u=Find(a[i].u);
int v=Find(a[i].v);
//printf("%d %d %d\n",a[i],u,a[i].v,a[i].w);
//cout << u << " " << v << endl;
if( u != v){
// Un(u,v);
pre[v]=u;
flag[a[i].u][a[i].v]=flag[a[i].v][a[i].u]=true;
ans+=a[i].w;
}
}
/*for(int i=1;i<=cnt;i++){
printf("%d ",pre[i]);
}
printf("\n");*/
}
void dfs(int x,int fa) //更新dp过程.(也就是加的优化,但也是最难懂的地方.b )
{
for(int i=2;i<=cnt;i++){
if(i==fa) continue;
if(flag[x][i])
{
//printf("%d %d\n",i,x);
if(dp[i].w != -1){ //v0和那些点不想连.
if(dp[x].w < G[x][i]) //dp(v)=max(dp(father(v)),w(father(v),v));
{
dp[i].w=G[x][i];
dp[i].u=x;
dp[i].v=i;
//printf("%d %d %d \n",dp[i].w,dp[i].u,dp[i].v);
}
else
dp[i]=dp[x];
}
//printf("%d %d\n",i,x);
dfs(i,x);
}
}
}
void solve()
{
int tmp[maxn],minn[maxn];
for(int i=1;i<=cnt;i++)
minn[i]=inf;
sort(a+1,a+1+n,cmp);
/*for(int i=1;i<=n;i++){
printf("%d %d %d\n",a[i].u,a[i].v,a[i].w);
}*/
kruskal();
//cout << ans << endl;
//minn[t]表示顶点1到连通分量t的最小边的权,tmp[t]表示连通分量t中与到顶点1的最小边相连的顶点.
for(int i=2;i<=cnt;i++){
if(G[1][i]!=-1){ //该点和那些点相连.
int t=Find(i); //找出根节点. 因为 t 不一定等于 i ;
if(minn[t]>G[1][i]) //求每个连通分量中和顶点1连接的最小权边
{ //这道题中就只有一个联通分量,所以minn中其实也只存有一个元素.
tmp[t] = i; //保存这个连通分量中直接与该点相连的点是哪个点.
minn[t] = G[1][i];
}
}
}
/*for(int i=1;i<=cnt;i++)
printf("%d ",minn[i]);
printf("\n");*/
int t=0; //t表示最小限度.
for(int i=1;i<=cnt;i++){
if(minn[i]!=inf){
t++;
flag[1][tmp[i]]=flag[tmp[i]][1]=true; //并把顶点1与连通分量连接起来.这样整个图就形成了一颗最小生成树了.
ans += G[1][tmp[i]];
}
} //得到最小m度生成树.
//cout << t << endl;
//有几个连通分量,就有t就等于多少.题目保证必有答案,所以t一定是<=m的.就算刚好等于,也会输出一个答案ans,即上面那个处理过了的ans.
for(int i=t+1;i<=m;i++){ //枚举t到m的所有最小生成树,即一步步将v1点的度加1,直到v1点的度为m为止.
//dp[v]为路径v0—v上与v0无关联且权值最大的边.
dp[1].w = -1;
for(int j=2;j<=cnt;j++){
if(flag[1][j])
dp[j].w = -1;
else
dp[j].w = 0 ;
}
dfs(1,-1);
/*for(int i=1;i<=cnt;i++){
printf("%d %d %d\n",dp[i].u,dp[i].v,dp[i].w);
}
printf("\n\n\n");*/ //dp[3]等于-inf是因为前面要在联通分量中选择一条最短的路劲与v0直接相连,所以他是-inf.
int tmp,Min=inf;
for(int j=2;j<=cnt;j++){
if(G[1][j]==-1) continue;
if(Min > G[1][j]-dp[j].w){
Min = G[1][j]-dp[j].w;
tmp = j;
}
}
if(Min>=0) //找不到这样的边,就说明已经求出
break;
flag[1][tmp]=flag[tmp][1]=true;
int u=dp[tmp].u;
int v=dp[tmp].v;
flag[u][v]=flag[v][u]=false;
ans+=Min;
}
printf("Total miles driven: %d\n",ans);
}
int main()
{
init();
solve();
}