限制度生成树

思路:

对于一个限制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;
}



                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值