二分答案
(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就是我们要找的最优解。然后根据这个下限进行分组,把得到的组数num和m进行比较,如果num>m则表示分组过多,则答案在mid+1到tot的区间内;如果num<m则表示分组过少,则答案在1到mid的区间内;如果num=m则在mid+1到tot的区间内寻找更优解。当左右指针重合时,最优解也就找到了。
参考代码:
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
10begin
11
12
13
14
15
16
17 end;
18 begin
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 end.
一个软件开发公司同时要开发两个软件,并且要同时交付给用户,现在公司为了尽快完成这一任务,
【输入格式】
输入文件第一行包含两个由空格隔开的整数n和m.其中1≤n≤100.1≤m≤l00.
接下来的n行每行包含两个用空格隔开的整数d1和d2,d1表示该技术人员完成第一个软件中的一个
【输出格式】
输出文件仅有一行包含一个整数d.表示公司最早能于d天后交付软件。
【样例输入】
3 20
1 1
2 4
1 6
【样例输出】
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)的区间中继续二分答案。
参考代码:
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原创,转载请注明出处)