pku3686 The Windy's 匹配好题!

引用:

[PKU][POJ][3686][THE WINDY'S]

–author: Answeror 
—title: [PKU][3686][The Windy's] 
—-link: http://acm.pku.edu.cn/JudgeOnline/problem?id=3686 
—-date: 2009-08-20 
-problem: 有m个工厂, n笔订单, 订单i在j厂完成所需时间为c[i][j], 要求一笔订单只能在一个厂完成, 一个厂不能同时做一笔以上订单, 求一个分配方案, 使得每个任务的平均完成时间最小. 
solution: 拆点, 以"第i个任务是j厂的倒数第k个任务"为顶点建图, KM算法求解二分图最小权匹配. 
—-code: http://docs.google.com/View?id=dgtsspfh_60hg9pwj2j

本题的建图方法很有意思, 我解这题的思维过程也挺有意思.

刚看到这题, 因为才做过[PKU][2516], 马上想到了带权二分匹配, 并且工厂和任务是一对多的关系, 很可能是要拆点, 把工厂拆开来. 于是马上开始敲键盘, 以订单为X集, 订单i在工厂j完成为Y集建图, 代码敲了几行感觉有点不对劲, 于是回过头再去看看题目要求什么, 一看果然是理解错了, 题目要求的是完成时间的平均值, 而不是生产时间的平均值, 完成时间还包括了等待的时间.

嗯, 那么Y集的状态应该改改, 既然主线是转化成带权二分匹配, 而且按这个数据规模来看不可能两边都是2500的顶点, 肯定有一边是50, 另一边是50或2500, 那么最后只有50条边被选中, 因为二分匹配中的每条边最多只累计一次, 所以不会产生"累加"的效果, 所以这50条边每条都必须能完全独立地表达"某订单在某工厂耗费的总时间". 第一反应当然是想把Y集顶点表示工厂j第k个订单, 当然这肯定是不对的, 前k-1个任务根本不能确定, 那这条边又怎么能完全独立地表达"某订单在某工厂耗费的总时间"呢?

再回到题目中去, 他要求平均时间, 就是总时间除n, 我们要求的是总时间, 总时间可以表示成每笔订单各自的完成时间相加, 把每笔订单各自的完成时间再写出来, 就是它之前的所有订单的生产时间加上它自己的生产时间, 而它之前的一个任务的完成时间再拆开来看…为什么要把每个订单的完成时间看成一个整体呢? 换个思路, 若订单i在厂j完成后, 后面还有k个任务, 那么这个总时间就还要加上k份i的生产时间. 那么就把Y集顶点表示成工厂j完成倒数第k个好了, 这样就把每笔订单的生产时间以及延误其他订单的时间看成一个整体(生产时间又可以看成是延误自己的时间). 后面就是套KM模板的事情了.

代码:
 1 Source Code
 2 
 3 Problem: 3686        User: yzhw
 4 Memory: 1024K        Time: 16MS
 5 Language: G++        Result: Accepted
 6 Source Code
 7 # include <cstdio>
 8 # include <cstring>
 9 using namespace std;
10 # define max(a,b) ((a)>(b)?(a):(b))
11 # define min(a,b) ((a)<(b)?(a):(b))
12 # define N 55
13 # define M 3000
14 int val[N][M],n,m,ln[N],rn[M],match[M];
15 bool l[N],r[M];
16 bool dfs(int pos)
17 {
18    l[pos]=true;
19    for(int i=0;i<n*m;i++)
20      if(!r[i]&&ln[pos]+rn[i]==val[pos][i])
21      {
22         r[i]=true;
23         if(match[i]==-1||dfs(match[i]))
24         {
25             match[i]=pos;
26             return true;
27         }
28      }
29      return false;
30 }
31 void adjust()
32 {
33     int minnum=0xfffffff;
34     for(int i=0;i<n;i++)
35       if(l[i])
36          for(int j=0;j<n*m;j++)
37            if(!r[j])
38               minnum=min(minnum,ln[i]+rn[j]-val[i][j]);
39     for(int i=0;i<n;i++)
40       if(l[i]) ln[i]-=minnum;
41     for(int i=0;i<n*m;i++)
42       if(r[i]) rn[i]+=minnum;
43       
44 }
45 int main()
46 {
47     int test;
48     scanf("%d",&test);
49     while(test--)
50     {
51         scanf("%d%d",&n,&m);
52         for(int i=0;i<n;i++)
53           for(int j=0;j<m;j++)
54           {
55             scanf("%d",&val[i][j]);
56             val[i][j]*=-1;
57           }
58         for(int k=1;k<=n;k++)
59            for(int i=0;i<n;i++)
60              for(int j=0;j<m;j++)
61                 val[i][(k-1)*m+j]=k*val[i][j];
62         memset(rn,0,sizeof(rn));
63         for(int i=0;i<n;i++)
64         {
65            int maxnum=-0xfffffff;
66            for(int j=0;j<n*m;j++)
67              maxnum=max(maxnum,val[i][j]);
68            ln[i]=maxnum;
69         }
70         memset(match,-1,sizeof(match));
71         for(int i=0;i<n;i++)
72         {
73             memset(l,0,sizeof(l));
74             memset(r,0,sizeof(r));
75             while(!dfs(i))
76             {
77               adjust();
78               memset(l,0,sizeof(l));
79               memset(r,0,sizeof(r));
80             }
81         }
82         int total=0;
83         for(int i=0;i<n*m;i++)
84           if(match[i]!=-1)
85             total-=val[match[i]][i];
86         printf("%.6f\n",(double)total/n);  
87     }
88     return 0;
89 }

posted on 2011-03-08 01:47 yzhw 阅读(264) 评论(0)  编辑 收藏 引用 所属分类: graph


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2011年3月>
272812345
6789101112
13141516171819
20212223242526
272829303112
3456789

导航

统计

公告

统计系统

留言簿(1)

随笔分类(227)

文章分类(2)

OJ

最新随笔

搜索

积分与排名

最新评论

阅读排行榜