最小度限制生成树

题目链接:http://poj.org/problem?id=1639

思路:先读入所有结点,初始化并查集,kruskal算法构成几个子树,读入v0结点和与其相连的节点,排序,将几个子树连接起来,遍历v0,递归求出从cnt度到K度的生成树 ,遍历n0条出边中没用过的出边,找到差额最小添删操作。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<string>
#include<vector>
#define inf 0x3f3f3f3f
using namespace std;
struct node{
    int a,b,w;
}edge[505];
struct node2{
    int to,w;
    bool cancel;
}v0[25]; // 记录v0点的出边 
int tree[25][25];  //记录把v0除外的生成森林(也就是各个连通分区,而不是包括V0的整棵生成树,这样操作是便于后面从v0遍历连接的连通分区,更新pathmax),用数组单独记录可以灵活修改 
int pathmax[25];  //记录v0点到其他点的路径上除了v0出边以外的最长边 
int maxedge[2][25]; //记录对应pathmax最长边的两个顶点,便于后面生成树的边的删除。 
bool vis[25]; //用于dfs过程中的标记 
int f[25];
int m,k,n,n0,ans; //n0是v0出边的总数 
map <string,int>IDache; //把字符型顶点转化成序号 
bool cmp1(const node &a,const node &b)
{
    return a.w<b.w;
}
bool cmp2(const node2 &a,const node2 &b)
{
    return a.w<b.w;
}
int getID(string &x)
{
    if(IDache.count(x)) return IDache[x];
    return IDache[x]=++n;             //将人转化为点,对应的就是i
}
void ini()
{
    IDache["Park"]=0;
    scanf("%d",&m);
    for(int i=0;i<m;i++){
        string s1,s2;
        int w;
        cin>>s1>>s2>>w;
        int t1=getID(s1),t2=getID(s2);
        edge[i].a=t1;
        edge[i].b=t2;
        edge[i].w=w;
    }
    scanf("%d",&k);
    for(int i=0;i<=n;i++) f[i]=i;
  
}
int getf(int x)
{
    return x==f[x]? x:f[x]=getf(f[x]);
}
int kruskal()
{
    memset(tree,0x3f,sizeof(tree)); //把树的各个顶点的距离初始化成inf表示没边 
    sort(edge,edge+m,cmp1);
	//先把V0除外去生成森林 
    for(int i=0;i<m;i++){ 
        if(!edge[i].a||!edge[i].b) continue;
        int x=getf(edge[i].a),y=getf(edge[i].b);
        if(x==y) continue;
        f[x] = y;
        ans+=edge[i].w;
        tree[edge[i].a][edge[i].b]=tree[edge[i].b ][edge[i].a ]=edge[i].w;
    }
    return ans;
}
//logV,从v0遍历生成树一次来记录v0点到其他点的路径上除了v0出边以外的最长边 
void dfs(int u,int v,int maxn,int maxnu,int maxnv) // maxnu,maxnv表示最长边的两个顶点 
{
    vis[u]=1;
    if(u!=0&&tree[u][v]>maxn){
        maxn=tree[u][v];
        maxnu=u;
        maxnv=v;
    }
    pathmax[v]=maxn;
    maxedge[0][v]=maxnu;
    maxedge[1][v]=maxnv;
    for(int i=0;i<=n;i++){
        if(tree[v][i]!=inf&&!vis[i]) dfs(v,i,maxn,maxnu,maxnv);
    }
 
}
int main()
{
    ini();
    ans=kruskal();
    //找到V0的出边 
    for(int i=0;i<m;++i){
		if(edge[i].a&&edge[i].b) continue;
		v0[n0].to = max(edge[i].a,edge[i].b);
		v0[n0].w = edge[i].w;
		v0[n0].cancel = false;
        n0++;
	}
    sort(v0,v0+n0,cmp2);//排序 ,便于后面把连通分区连通的操作 
    int cnt=0;
    //连通各个连通分区,合成一棵树 
    for(int i=0;i<n0;i++){
        int x=getf(v0[i].to),y=getf(0);
        if(x==y) continue;
        f[x] = y;
        ans+=v0[i].w;
        v0[i].cancel=true; //这条出边已用过,标记,下次不需再用了 
        cnt++;
        memset(vis,0,sizeof(vis));
        dfs(0,v0[i].to,0,0,0);   //0->v0,v0[i].to->v0可联通的点 
    }
	//递归求出从cnt度到K度的生成树 
    for(int j=0;j<k-cnt;j++){
        int minn=inf;
        int t,book;
        for(int i=0;i<n0;i++){ 	//遍历n0条出边中没用过的出边,找到差额最小添删操作 
            if(v0[i].cancel) continue;
            int to=v0[i].to;
            if(minn>v0[i].w-pathmax[to]){
                minn=v0[i].w-pathmax[to];
                t=to;
                book=i;
            }
        }
        if(minn>=0) break;//如果连最小的差额都是正数,那么就不可能再让当前的生成树更小了,已是最优解break。 
        ans+=minn;
        tree[maxedge[0][t] ][maxedge[1][t]]=tree[maxedge[1][t]][maxedge[0][t]]=inf;//森林删边 
        v0[book].cancel=true;
        memset(vis,0,sizeof(vis));
        dfs(0,t,0,0,0); //维护V0与这个连通分量的pathmax数组。 
    }
    printf("Total miles driven: %d\n",ans);
    return 0;
 
}

代码为转(chao)载(xi)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值