解题报告 poj 1639


1. 题目  poj   1639
Description
The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible ability to cram an unlimited number of themselves into even the smallest vehicle. During the off-season, the brothers like to get together for an Annual Contortionists Meeting at a local park. However, the brothers are not only tight with regard to cramped quarters, but with money as well, so they try to find the way to get everyone to the party which minimizes the number of miles put on everyone's cars (thus saving gas, wear and tear, etc.). To this end they are willing to cram themselves into as few cars as necessary to minimize the total number of miles put on all their cars together. This often results in many brothers driving to one brother's house, leaving all but one car there and piling into the remaining one. There is a constraint at the park, however: the parking lot at the picnic site can only hold a limited number of cars, so that must be factored into the overall miserly calculation. Also, due to an entrance fee to the park, once any brother's car arrives at the park it is there to stay; he will not drop off his passengers and then leave to pick up other brothers. Now for your average circus clan, solving this problem is a challenge, so it is left to you to write a program to solve their milage minimization problem.
Input
Input will consist of one problem instance. The first line will contain a single integer n indicating the number of highway connections between brothers or between brothers and the park. The next n lines will contain one connection per line, of the form name1 name2 dist, where name1 and name2 are either the names of two brothers or the word Park and a brother's name (in either order), and dist is the integer distance between them. These roads will all be 2-way roads, and dist will always be positive.The maximum number of brothers will be 20 and the maximumlength of any name will be 10 characters.Following these n lines will be one final line containing an integer s which specifies the number of cars which can fit in the parking lot of the picnic site. You may assume that there is a path from every brother's house to the park and that a solution exists for each problem instance.
Output
Output should consist of one line of the form
Total miles driven: xxx
where xxx is the total number of miles driven by all the brothers' cars.
Sample Input
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 43
Bernardo Park 19
Bernardo Clemenzi 82
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 109
Park Herb 24
Herb Eduardo 79
3
Sample Output
Total miles driven: 183
Source
East Central North America 2000

2. 题目翻译
题目大意是:
矮人虽小却喜欢乘坐巨大的轿车,轿车大到可以装下无论多少矮人。某天,N(N≤5000)个矮人打算到野外聚餐。为了集中到聚餐地点,矮人A要么开车到矮人B家中,留下自己的轿车在矮人B家,然后乘坐B的轿车同行;要么直接开车到聚餐地点,并将车停放在聚餐地。
虽然矮人的家很大,可以停放无数量轿车,但是聚餐地点却最多只能停放K辆轿车。现在给你一张加权无向图,它描述了N个矮人的家和聚餐地点,要你求出所有矮人开车的最短总路程。

    [输入文件]
第一行是整数M(M<=49000),接下来M行描述了M条道路。每行形式如同:S1 S2 x,S1和S2均是大于0小于5000的整数,x小于等于1000。最后一行包含两个整数k,root。root表示聚餐的地点。

    [输出文件]
仅一行,形式如同:Total miles driven: xxxXxx是整数,表示最短总路程。
3. 算法
这是一道标准的最小限度生成树(就是图中的某些点在生成树时有度的限制,找满足这些约束的最小生成树。如果所有点都有度限制,那么这个问题将是NP难题),具体算法如下:
1. 先求出最小 m 度限制生成树:原图中去掉和 V0 相连的所有边(可以事先存两个图, Ray 的方法是一个邻接矩阵,一个邻接表,用方便枚举边的邻接表来构造新图),得到 m 个连通分量,则这 m  个连通分量必须通过 v0 来连接,所以,在图 G  的所有生成树中 dT(v0)≥m 。也就是说,当 k<m 时,问题无解。对每个连通分量求一次最小生成树(哪个算法都行),对于每个连通分量 V’ ,用一条与 V0 直接连接的最小的边把它与 V0 点连接起来,使其整体成为一个生成树。于是,我们就得到了一个 m 度限制生成树,不难证明,这就是最小 m 度限制生成树。
2. 由最小 m 度限制生成树得到最小 m+1 度限制生成树;:连接和 V0 相邻的点 v ,则可以知道一定会有一个环出现(因为原来是一个生成树),只要找到这个环上的最大权边(不能与 v0 点直接相连)并删除,就可以得到一个 m+1 度限制生成树,枚举所有和 V0 相邻点 v ,找到替换后,增加权值最小的一次替换 (当然,找不到这样的边时,就说明已经求出) ,就可以求得 m+1 度限制生成树。。如果每添加一条边,都需要对环上的边一一枚举,时间复杂度将比较高(但这个题数据比较小,所以这样也没问题,事实上,直接枚举都能过这个题),这里,用动态规划解决。设   Best(v) 为路径 v0—v 上与 v0 无关联且权值最大的边。定义 father(v) 为 v 的父结点,由此可以得到动态转移方程: Best(v)=max(Best(father(v)),ω(father(v),v)) ,边界条件为 Best[v0]=-∞ (因为我们每次寻找的是最大边,所以 -∞ 不会被考虑) ,Best[v’]=-∞| (v0,v’)∈E(T) 。
3. 当 dT(v0)=k 时停止(即当 V0 的度为 k 的时候停止),但不一定 k 的时候最优。

接下来说一下算法的实现:
采用并查集+ kruskal 代码量还少一点。
首先, 每个连通分量的的最小生成树可以直接用一个循环,循环着 Kruskal 求出。这里利用了联通分量间的独立性,对每个连通分量分别求最小生成树,和放在一起求,毫不影响。而且kruskral算法保证了各连通分量边的有序性。
找最小边的时候,上面讲的动态规划无疑是一种好方法,但是也可以这么做:
先走一个循环,但我们需要逆过来加边,将与v0关联的所有边从小到达排序,然后将各连通分量连接起来,利用并查集可以保证每个连通分量只有一条边与v0相连,由于边已经从小到达排序,故与每个连通分量相连的边就是每个连通分量与v0相连中的最小边。
然后求 m+1 度的最小生成树时,可以直接用 DFS ,最小生成树要一直求到 k 度,然后从中找出一个最优值。
4. 注意事项
5. 时空复杂度
6. 程序代码
并查集+kruskal    (C++)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define inf 0xffffff
#define NUM 22

struct info{
 int s,e,w,flag,next,ith;
}edge[NUM * NUM],item[NUM],edge2[NUM * NUM]; // edge排序要用到,且存所有边,item存与v0相关联的边,edge2备份所有边

char peo[NUM][15];
int first[NUM];
int f[NUM];
int sign[NUM];
int tol,n;

void Add(int s,int e,int w)
{
 edge[tol].s = s;
 edge[tol].e = e;
 edge[tol].w = w;
 edge[tol].flag = 0;
 edge[tol].ith = tol;
 edge2[tol] = edge[tol];
 edge2[tol].next = first[s];
 first[s] = tol ++;
}

int find(char *s)
{
 int i;
 for(i = 0; i < n; i ++){
  if( !strcmp(s,peo[i]) ) return i;
 }
 strcpy(peo[n ++],s);
 return i;
}

int cmp(const void *a,const void *b)
{
 struct info *c,*d;
 c = (struct info *)a;
 d = (struct info *)b;
 return c->w - d->w;
}

int find_father(int s)
{
 if(s != f[s]){
  f[s] = find_father(f[s]);
 }
 return f[s];
}

void Union(int a,int b)
{
 f[f[a]] = f[b];
}

int v0;

int dfs(int s,int max,int e) // s待扩展的点,max扩展到s时的最大边权,e最大边权对应的边号
{
 int temp,k,t,mid;
 sign[s] = 1;
 for(temp = first[s];temp != -1;temp = edge2[temp].next ){
  if(edge2[temp].flag && !sign[edge2[temp].e]){

   if(edge2[temp].e == v0) return e;

   t = edge2[temp].w > max ? temp : e;

   k = edge2[temp].w > max ? edge2[temp].w : max;

   t = dfs(edge2[temp].e, k , t);

   if( t != -1 ) return t;

   sign[edge2[temp].e] = 0;
  }
 }
 return -1;
}

int main()
{
 int m,i,j,w,vn,count,min,ans;
 int fa,fb,res,temp,k,max_e,mid_i;
 char nam[2][15];
 n = tol = 0;
 memset(first,-1,sizeof(first));

 scanf("%d",&m);
 while(m --){
  scanf("%s%s%d",nam[0],nam[1],&w);
  i = find(nam[0]);
  j = find(nam[1]);
  Add(i,j,w);  //添加正向边和反向边
  Add(j,i,w);
 }
 scanf("%d",&k);
 v0 = find("Park");
    for(temp = first[v0],i = 0;temp != -1; temp = edge2[temp].next,i ++) item[i] = edge2[temp];
 vn = i;
 for(i = 0; i < n;i ++)f[i] = i;
 qsort(edge,tol,sizeof(struct info),cmp);
 for(res = i = 0; i < tol;i ++){           //第2步
  if(edge[i].s == v0 || edge[i].e == v0) continue;
  fa = find_father(edge[i].s);
  fb = find_father(edge[i].e);
  if(fa != fb){
   Union(edge[i].s,edge[i].e);
   edge2[ edge[i].ith ].flag  = 1;
   edge2[ edge[i].ith^1 ].flag  = 1;
   res += edge[i].w;
  }
 }
 qsort(item,vn,sizeof(struct info),cmp);
 for(count = i = 0; i < vn;i ++){      //第3步 复制第2步稍加修改即可
  fa = find_father(item[i].s);
  fb = find_father(item[i].e);
  if(fa != fb){
   Union(item[i].s,item[i].e);
   edge2[item[i].ith].flag = 1;
   edge2[ item[i].ith^1 ].flag  = 1;
   res += item[i].w;
   count ++;
  }
 }
 ans = res;    // ans记录最终的答案
 while(count < k){         // 扩展度
  min = inf;
  for(i = first[v0];i != -1;i = edge2[i].next ){
   if(edge2[i].flag == 0){
    memset(sign,0,sizeof(sign)); 
    temp = dfs(edge2[i].e,-inf,i);      //返回最大权值边的编号,这里本没有树,因为边的存在而有了树,
    if(res - edge2[temp].w + edge2[i].w < min){      ////////////////做到心中有“树”
     min = res - edge2[temp].w + edge2[i].w;
     mid_i = i;      //记录要加入边的序号
     max_e = temp;        //要删除边的序号
    }
   }
  }
  if(min == inf) break;
  edge2[mid_i].flag = 1;        //加
  edge2[mid_i^1].flag = 1;
  edge2[max_e].flag = 0;        //删
  edge2[max_e^1].flag = 0;
  res = min; 
  if(res < ans)ans = res;     //取最小
  count ++;
 }
 printf("Total miles driven: %d\n",ans);
 return 0;
}

Prim+DP(pascal)
program p1639;
//
const
    maxn=30;
    maxm=1000;
    oo=19920905;
//
var
    name:array[0..maxn] of string;
    re:array[0..maxn,0..maxn] of longint;
    num:array[0..maxn] of longint;
    vis:array[0..maxn] of boolean;
    use:array[0..maxn,0..maxn] of boolean;
    pre:array[0..maxn] of longint;
    d:array[0..maxn] of longint;
    size:array[0..maxn] of longint;
    minset:array[0..maxn] of record w,p:longint; end;
    big:array[0..maxn] of longint;
    n,es:longint;
    tot,ans:longint;
    du,maxdu:longint;
//
function min(x,y:longint):longint;
begin
    if x<y then exit(x) else exit(y);
end;
function max(x,y:longint):longint;
begin
    if x>y then exit(x) else exit(y);
end;
//
function getnum:longint;
var ch:char;
begin
    getnum:=0;
    read(ch);
    while (ch<'0') or (ch>'9') do read(ch);
    while (ch>='0') and (ch<='9')  do
    begin
        getnum:=getnum*10+ord(ch)-48;
        read(ch);
    end;
end;
//
function getname:longint;
var ch:char;
    tmp:string;
    i:longint;
    function ok(ch:char):boolean;
    begin
        if (ch<='z') and (ch>='a') then exit(true);
        if (ch<='Z') and (ch>='A') then exit(true);
        exit(false);
    end;
begin
    tmp:='';
    read(ch); while not ok(ch) do read(ch);
    while ok(ch) do
    begin
        tmp:=tmp+ch;
        read(ch);
    end;
    for i:=1 to n do
        if name[i]=tmp then exit(i);
    inc(n); name[n]:=tmp;
    exit(n);
end;
//
procedure init;
var m,i,j:longint;
    x,y,z:longint;
begin
    readln(m); n:=1; name[1]:='Park'; es:=0;
    for i:=1 to maxn do for j:=1 to maxn do re[i][j]:=oo;
    for i:=1 to m do
    begin
        x:=getname; y:=getname; z:=getnum;
        re[x][y]:=min(re[x][y],z);
        re[y][x]:=min(re[y][x],z);
    end;
    maxdu:=getnum
end;
//
procedure dfs(x,t:longint);
var i:longint;
begin
    vis[x]:=true; num[x]:=t;
    inc(size[t]);
    for i:=1 to n do
        if (re[x][i]<oo) and (not vis[i]) then
        begin
            dfs(i,t);
        end;
end;
//
procedure prime(v0:longint);
var i,k,minn,root,p:longint;
begin
    fillchar(vis,sizeof(vis),0);
    for i:=1 to n do
        if num[i]=v0 then
        begin
            root:=i;
            break;
        end;
    for i:=1 to n do d[i]:=oo;
    d[root]:=0; vis[1]:=true;
    for k:=1 to size[v0] do
    begin
        minn:=oo;
        for i:=1 to n do
            if (not vis[i]) and (d[i]<minn) then
            begin
                minn:=d[i];
                p:=i;
            end;
        ans:=ans+d[p];
        vis[p]:=true;
        for i:=1 to n do
            if (not vis[i]) and (re[p][i]<d[i]) then
            begin
                d[i]:=re[p][i];
                pre[i]:=p;
            end;
    end;
end;
//
procedure deepsearch(x,fa:longint);
var i:longint;
begin
    pre[x]:=fa;
    if fa<>1 then big[x]:=max(big[fa],re[x][fa]) else big[x]:=0;
    for i:=1 to n do
        if (use[x][i]) and (i<>fa) then
            deepsearch(i,x);
end;
//
procedure work;
var i,p,maxx:longint;
begin
    inc(tot);
    fillchar(num,sizeof(num),0);
    fillchar(vis,sizeof(vis),0);
    fillchar(size,sizeof(size),0);
    num[1]:=0; tot:=0; vis[1]:=true;  size[0]:=1;
    for i:=1 to n do
        if not vis[i] then
        begin
            inc(tot);
            dfs(i,tot);
        end;
    ans:=0;
    for i:=1 to tot do
        prime(i);
    for i:=1 to tot do minset[i].w:=oo;
    for i:=1 to n do
        if re[i][1]<oo then
            if re[i][1]<minset[num[i]].w then
            begin
                minset[num[i]].w:=re[i][1];
                minset[num[i]].p:=i;
            end;
    fillchar(use,sizeof(use),0);
    for i:=2 to n do begin use[pre[i]][i]:=true; use[i][pre[i]]:=true; end;
    du:=0;
    for i:=1 to tot do
    begin
        ans:=ans+minset[i].w;
        use[1][minset[i].p]:=true;
        use[minset[i].p][1]:=true;
        inc(du);
    end;
    fillchar(big,sizeof(big),0);
    re[0][1]:=0; re[1][0]:=0;
    deepsearch(1,0);
    while du<maxdu do
    begin
        maxx:=0; p:=-1;
        for i:=2 to n do
            if not use[1][i] then
                if big[i]-re[1][i]>maxx then
                begin
                    maxx:=big[i]-re[1][i];
                    p:=i;
                end;
        if p=-1 then exit;
        i:=p;
        while re[pre[i]][i]<>big[p] do i:=pre[i];
        use[i][pre[i]]:=false; use[pre[i]][i]:=false;
        pre[p]:=1; deepsearch(p,1);
        inc(du); ans:=ans-maxx;
    end;
end;
//
procedure outit;
begin
    writeln('Total miles driven: ',ans);
end;
//
begin
    assign(input,'p1639.in');reset(input);
    assign(output,'p1639.out');rewrite(output);
    init;
    work;
    outit;
    close(input);close(output);
end.

7. 参考资料
国家集训队 2004 论文       汪汀:最小生成树问题的扩展
www.baidu.com

posted @ 2011-08-10 17:06  木小漾  阅读(605)  评论(0编辑  收藏  举报