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

这是最小度限制生成树的经典问题,题意就不说了

题目链接: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;

}
时间: 2024-08-07 08:22:52

POJ 1639 Picnic Planning(初遇最小度限制生成树)的相关文章

(最大k度限制生成树)POJ 1639 - Picnic Planning

题意: 给一个无向带权图,图上有不超过20个人和1个公园,现在这些人想到公园去集合,他们可以直接去公园也可以,和其他人一起坐车去公园(假设他们的车容量无限),但是这个公园停车场只有k个位置,现在要求他们到达公园所需要的总花费. 分析: 乍一看是最小生成树,但是停车场只有k个位置,所以就限定了公园节点只能最多连k个人,也就是说有一个点的度数是给定了的. 想了很久,第一感觉就是想其他生成树那样,肯定要把边删去,从环中选择更优解, 按套路来说. 但是想了很久不知道怎么处理. 不过,看到题目只有20个人

poj 1639 Picnic Planning 度限制mst

https://vjudge.net/problem/POJ-1639 题意: 有一群人,他们要去某一个地方,每个车可以装无数个人,给出了n条路,包含的信息有路连接的地方,以及路的长度,路是双向的,但是终点只有一个,并且终点能停的车的数量是有限制的,问最少走的路是多少. 思路: 因为终点的停车的数量是有限制的,所以终点的度是有限制的,又因为这题可以用最小生成树解决,所以就是度限制最小生成树. 度限制最小生成树求解思想并不复杂,首先我们把有度限制的点给忽略,然后给每一个连通分量求最小生成树,最后把

POJ 2728 Desert King(初遇最优比率生成树)

题目链接:http://poj.org/problem?id=2728 题意:给出几个村庄的坐标x[i]和y[i],以及海拔z[i].要在这些村庄之间建水渠,费用和两个村庄的海拔差成正比,水渠长度和村庄二维坐标(x,y)上的距离成正比,要求一种方案使得(总的花费/总的水渠长度)最小,输出这个最小值,保留三位小数. 这是一道0,1分数规划的题目,求的是一棵生成树sigma(dh)/sigma(l) 的最小值(dh是树上两点之间的高度差,l是树上两点之间的边长). 按0,1分数规划的思路对解进行分析

poj1639 Picnic Planning,K度限制生成树

题意: 矮人虽小却喜欢乘坐巨大的轿车,车大到可以装下无论多少矮人.某天,N(N≤20)个矮人打算到野外聚餐.为了集中到聚餐地点,矮人A 要么开车到矮人B 家中,留下自己的轿车在矮人B 家,然后乘坐B 的轿车同行:要么直接开车到聚餐地点,并将车停放在聚餐地.虽然矮人的家很大,可以停放无数量轿车,但是聚餐地点却最多只能停放K 辆轿车.给你一张加权无向图,描述了N 个矮人的家和聚餐地点,求出所有矮人开车最短总路程. 单点K度限制最小生成树 算法步骤: 1.求出除去K度点的最小生成森林,设森林数为m 2

poj1639,uva1537,uvalive2099,scu1622,fzu1761 Picnic Planning (最小限制生成树)

Picnic Planning Time Limit: 5000MS   Memory Limit: 10000K Total Submissions: 10742   Accepted: 3885 Description The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible ability to cram an unlimited number of the

K度限制MST poj 1639

/* k度限制MST:有一个点的度<=k的MST poj 1639 要求1号点的度不超过k 求MST 我们先把1号点扔掉 跑MST 假设有sum个连通分支 然后把这sum个分支连到1上 就得到了一个sum度的MST 这里往上连的时候 我们连这个分支里 距离1最近的 然后我们在1上加一条边 (即加一个度)得到sum+1度的MST 这里加边的时候 1连出去的每一条边都试一遍 取最小 假设当前1连到了 i 因为原来是个树 这样一搞就形成一个环 我们现在要删去环里面最长边 来得到更小的ans 我么维护d

次小生成树 最小度限制生成树

首先说明这是一个坑! 因为发现啊次小生成树为什不用树链剖分写(虽然麻烦但是思路各种清晰!),最小度限制生成树可以用lct写(而且是似乎要比那个直接写的算法容易因为要各种建边删边dfs(就没有考虑过时间么)!!!!)!!!(好像是错的) (因为是自己傻叉写不出来) (半小时后觉得还是自己傻叉了……下面那个代码应该继续敲就行了,一个是最小生成树还没快排,然后在求最小度限制生成树时用邻接矩阵比较简单(反正时间复杂度也是o(n²)),然后每次就直接dfs一遍,dfs的时候传两个参,一个是这个节点,一个是

poj 2728 Desert King(最优比率生成树,01分数规划)

http://poj.org/problem?id=2728 大致题意:有n个村庄,输入每个村庄的位置和高度,这n个村庄要连在一起,村与村之间的长度为他们之间的欧几里得距离,花费是两村之间的高度差,要求连在一起的花费和与距离和之比的最小值. 思路:明显的最优比率生成树.二分答案λ,每条边重新赋权c[i] - λd[i] ,因为要求比值最小,那么对于所有的生成树,它们的f[λ]必须>=0,所以只需求得基于最小生成树的f'[λ],当f'[λ] = 0时即找到了正解λ*. 二分: #include <

【POJ 1639】 Picnic Planning (最小k度限制生成树)

[题意] 有n个巨人要去Park聚会.巨人A和先到巨人B那里去,然后和巨人B一起去Park.B君是个土豪,他家的停车场很大,可以停很多车,但是Park的停车场是比较小.只能停k辆车.现在问你在这个限制条件下.巨人到达Park的最短距离. 如果把那个条件去掉.那么就是就是求最小生成树.加上那个条件其实就是求顶点度数限制为k的最小生成树. Input Input will consist of one problem instance. The first line will contain a s