二分答案

二分答案

  (2011-09-21 18:18:21)
标签: 

杂谈


from:http://www.cnblogs.com/saltless/archive/2010/08/05/1793322.html

有人看到“二分答案”这个题目,可能会很不解。题目过程可以二分,答案怎么也能二分呢?

事实上,当你看到找极大值中的最小值或者求极小值中的最大值的题目时,二分答案或许是一个不错的选择。根据数据范围判断是否选用二分(二分能把时间复杂度降到log n),再和上文所说的条件结合,一般就不会错了。

先看一个例题。


【描述】
现在某组织中(记作R)有n个人,他们的联络网形成一棵以Saltless为根的树,有边相连代表两人可以直接联络。
每个人有一个代号,Saltless代号为1,且除Saltless外每个人的父节点的代号小于他自己的代号。
由于某些原因,Saltless给R的成员分别下达紧急任务,R需要分成m组行动,每个组必须满足如下条件:
1、每个组员仅分在本组中
2、至少有一个组员
3、任意两个组员无需通过本组外的人就可以联络(但可以通过本组组员进行联络)

每个人有一个能力指数,一个组的能力指数是全组人能力指数之和。
对于任意一种正确的分组,平均度就是m组中最小能力指数。为了分组较为平均,Saltless希望平均度尽可能大。

【格式】
PROGRAM NAME: organ

INPUT FORMAT:
第一行为三个数:n,m,和Saltless的能力指数(1<=m<=n<=10000)。
接下来n-1行,每行两个数:此人的父节点代号和他的能力指数(能力指数值为正整数,不超过30,行数就是他本身的代号)

OUTPUT FORMAT:
输出格式: 一个数,表示最大的平均度。

SAMPLE INPUT (organ.in)
7 2 2
1 4
1 5
2 1
2 2
3 4
4 3

SAMPLE OUTPUT (organ.out)
10


【Hint】
分组:{1,3,6},{2,4,5,7}

看到这个题,出口很明显,可能首先想到DFS。但是10000的数据DFS显然是不能承受的。这时候我们采用二分答案。

我们先记录下所有人能力指数之和tot,然后假设它的中间值mid就是我们要找的最优解。然后根据这个下限进行分组,把得到的组数numm进行比较,如果num>m则表示分组过多,则答案在mid+1tot的区间内;如果num<m则表示分组过少,则答案在1mid的区间内;如果num=m则在mid+1tot的区间内寻找更优解。当左右指针重合时,最优解也就找到了。

 

参考代码:

1 program sai;
2 var
3 m,n,tot:longint;
4 mid,num,l,r:longint;
5 i:integer;
6 p,w,t:array[0..10000]of longint;
7 function get(x:longint):longint; //分组
8 var
9    i,num:integer;
10begin
11   num:=0;
12   t:=w;
13   for i:=n downto 1 do //从儿子找起,以消除后效性
14   if t[i]<x then t[p[i]]:=t[p[i]]+t[i]
15       else inc(num);//如果一个组的能力指数之和达到了中间值就加一组
16    exit(num);
17 end;
18 begin
19    assign(input,'sai.in');
20    reset(input);
21    assign(output,'sai.out');
22    rewrite(output);
23    readln(n,m,tot);
24    w[1]:=tot;
25    for i:=2 to n do
26      begin
27        readln(p[i],w[i]);
28        tot:=tot+w[i];
29      end;
30     l:=1;
31     r:=tot;
32     while l<=r do
33       begin
34          mid:=(l+r)>>1; //mid记录了临时的最优解
35          num:=get(mid);
36          if num>=m then l:=mid+1 else r:=mid-1; //与组数进行比较,确定下一个搜索区间
37        end;
38     writeln(r); //右指针记录的是最优解
39     close(input);
40     close(output);
41 end.




   话说二分答案是个好东西,好打好用~可是对于二分答案解的正确性的验证的方法却有许多。这道题是经典的DP验证,对于这段DP验证可不是那么好想。恩,切入正题。

【题目描述】
一个软件开发公司同时要开发两个软件,并且要同时交付给用户,现在公司为了尽快完成这一任务,
将每个软件划分成m个模块,由公司里的技术人员分工完成。每个技术人员完成同一软件的不同模块的所
用的天数是相同的,并且是已知的.但完成不同软件的一个模块的时间是不同的,每个技术人员在同一
刻只能做一个模块,一个模块只能由一个人独立完成而不能由多人协同完成。一个技术人员在整个开
期内完成一个模块以后可以接着做任一软件的任一模块。写一个程序.求出公司最早能在什么时候交
软件。
【输入格式】
输入文件第一行包含两个由空格隔开的整数n和m.其中1≤n≤
1001≤m≤l00.
接下来的n行每行包含两个用空格隔开的整数d1和d2,d1表示该技术人员完成第一个软件中的一个
模块所需的天数,d2表示该技术人员完成第二个软件中的一个模块所需的天数.其中1≤d1,d2<=100
【输出格式】
输出文件仅有一行包含一个整数d.表示公司最早能于d天后交付软件。
【样例输入】
3 20
1 1
2 4
1 6
【样例输出】
18
【注释】
最快的方案是第一个技术人员完成第二个软件的18个模块.用时18天,第三个技术人员完成第一个
软件的18个模块,用时18天.其余的模块由第二个技术人员完成,用时12天,做完所有模块需要18天。
如果第一个技术人员完成第二个软件的17个模块.第三个技术人员完成第一个软件的17个模块,其余的
模块由第二个技术人员完成.需要用时18天.做完所有模块仍然需要18天,所以少于18天不可能做完所
有模块。

又是有关分组的问题,自然而然,可以考虑二分答案。我们用mid来二分最小时间。这样,用F[i,j]表示i个人完成j个第一个项目的模块后还能做的第二个项目的模块总数。只要求出F[n,m]的值,将它和m比较,就知道在mid的时间内,保证做完m个项目一,是不是能做完m个项目二的模块。如果f[n,m]>m,那么mid就是可行解,我们就可以在这个基础上再进行二分,找到最优解。

有了这个思路,动归方程也很明了了。

F[i,j]=max{F[i-1,j-k]+((mid-a[i]*k)div b[i])} (k<=j)

这里k表示只做k个第一个项目的模块,把mid-a[i]*k的时间用来做第二个项目。

注意几点:首先,要注意k<=j这个条件,否则F数组会超界。其次,将F数组初始化成负值是有必要的,因为会出现由于专注于做项目一而不做项目二的情况。这样F[0,0]要初始化成0。另外,二分答案的区间要定够。由于是二分,多定一些关系不大,就是多找几次的事,但是定小了就悲剧了~最后,由于刚开始找出的mid只是可行解,所以如果出现满足f[n,m]>m的情况时应该记录可行解,在[l,mid-1)的区间中继续二分答案。

参考代码:

1 program software;
2 var
3 f:array[0..100,0..100]of integer;
4 i,j,l,r,maxx:integer;
5 n,m,mid,ans,c:integer;
6 a,b:array[1..100]of integer;
7 function max(x,y:longint):longint;
8 begin
9 if x>y then exit(x)
10 else exit(y);
11 end;
12 function check(x:longint):longint;
13 var
14 i,j,k:integer;
15 begin
16 for i:=0 to n do
17 for j:=0 to m do
18 f[i,j]:=-maxint; //初始化成负值
19 f[0,0]:=0;
20 for i:=1 to n do
21 for j:=0 to m do
22 for k:=0 to x div a[i] do
23 if k<=j then
24 f[i,j]:=max(f[i,j],f[i-1,j-k]+((x-a[i]*k)div b[i]));
25 exit(f[n,m]);
26 end;
27 begin
28 readln(n,m);
29 for i:=1 to n do
30 begin
31 readln(a[i],b[i]);
32 if a[i]>maxx then maxx:=a[i];
33 if b[i]>maxx then maxx:=b[i]; //找出最慢的,乘以总模块数为二分上界
34 end;
35 maxx:=maxx*m;
36 l:=(m*2)div n;
37 r:=maxx;
38 while l<=r do
39 begin
40 mid:=(l+r)div 2;
41 c:=check(mid);
42 if c>=m then
43 begin
44 r:=mid-1;
45 ans:=mid;
46 end
47 else l:=mid+1;
48 end;
49 writeln(ans);
50 end.

(Saltless原创,转载请注明出处)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值