poj 1639 Picnic Planning 最小度限制生成树 (模版)

题解最小度限制生成树 ,详解减:

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年的国家集训队论文,讲的很详细。

View Code
#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();
    }
}
原文地址:https://www.cnblogs.com/acSzz/p/2613115.html