您的位置:首页 > 其它

POJ 1639 Picnic Planning(初遇最小度限制生成树)

2015-02-11 20:56 477 查看
这是最小度限制生成树的经典问题,题意就不说了

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

一般都是1个顶点的度有限制k,如果每个顶点的度都有限制,那么当前是NP难的。

为了解决这个题目,先把限制度数的点设为V0点,那么把这一点先除外,那么剩下的点都没有度数限制,所有先对他们进行分析,把他们求生成森林后,假设得到t个连通分量,所以为了生成一棵把v0包含在内的树,必须让v0的度数限制度数k>=t,如果<t,无解。

接下来k度中已经用掉了t度,如何求从t度到k度的最小生成树呢,还是添边成环再删边的思想,所以:遍历从V0的出边找到一条删掉的边比添加的边差值最大的那一方案,也就是让t+1度生成树尽可能小。就这样循环k-t次即可。当然如果某一次历遍从v0的出边(没用过的出边)以后,发现每种方案成环删掉的边都要比添边小,那么生成树不可能再变小,所以已是最优解。

思路很简单,但是代码实现的确有点难...我的代码实现用的是kruskal总的复杂度是log(Vk+Elog|E| ),难点在于找到最小差值添删操作,我用dfs维护v0到各个连通分量间的各个顶点路径间的最大边。然后用tree[ ][ ]数组记录v0外生成森林的边,可以任意修改添删。

另外这个ppt http://wenku.baidu.com/view/70ef0e00eff9aef8941e06db.html 说的不错,

还有黑书p300~303给了严谨和较详细的证明,实现方法比较说的比较粗略

//256 KB	16 ms
#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;
}
void ini()
{
    IDache["Park"]=0;
    scanf("%d",&m);
    for(int i=1;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]);
}
void unite(int x,int y)
{
    int t1=getf(x),t2=getf(y);
    if(t1==t2) return;
    f[y]=x;
}
int kruskal()
{
    memset(tree,0x3f,sizeof(tree)); //把树的各个顶点的距离初始化成inf表示没边 
    sort(edge+1,edge+1+m,cmp1);
	//先把V0除外去生成森林 
    for(int i=1;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;
        unite(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=1;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=1;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;
    }
    sort(v0+1,v0+n0+1,cmp2);//排序 ,便于后面把连通分区连通的操作 
    int cnt=0;
    //连通各个连通分区,合成一棵树 
    for(int i=1;i<=n0;i++){
        int x=getf(v0[i].to),y=getf(0);
        if(x==y) continue;
        unite(x,y);  
        ans+=v0[i].w;
        v0[i].cancel=true; //这条出边已用过,标记,下次不需再用了 
        cnt++;
        memset(vis,0,sizeof(vis));
        dfs(0,v0[i].to,0,0,0);
    }
	//递归求出从cnt度到K度的生成树 
    for(int j=1;j<=k-cnt;j++){
        int minn=inf;
        int t,book;
        for(int i=1;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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: