二分答案
如果已知候选答案的范围[min,max],有时候我们不必通过计算得到答案,只需在此范围内应用“二分”的过程,逐渐靠近答案(最后,得到答案)!
一、何时可以使用“二分答案”
不是任何题目都适合使用“二分答案”的,我Sam观察到一般有以下的一些特征:
A. 候选答案必须是离散的 ,且已知答案的范围是:[最小值min, 最大值max] (连续区间上不能进行二分操作)
例如,在题目“Kth Largest 第k大的数”中 ==> 答案是闭区间[a[1]b[1], a[n]b[n]]上的正整数
例如,在题目“Drying 烘干衣服”中 ==> 烘干时间t∈[0,maxT], maxT=max{a[i]}
B. 候选答案在区间[min,max]上某种属性一类一类的排列 (这样就能在此属性上进行二分操作 ),各个类别不交错混杂
例如,在题目“Kth Largest 第k大的数”中 ==>
(候选答案)第k大的数的值: a[1]b[1], ... , a[n]b[n]
(属性分类)>这个乘积的数有多少: n^2-1 ... 0
例如,在题目“Drying 烘干衣服”中 ==>
(候选答案)烘干时间: t=0, t=1, t=2, t=3, ..., t=maxT-1, t=maxT
(属性分类)能否烘干: 不能 不能 不能 能 ... 能 能
C. 容易判断某个点 是否为答案(即二分过程中,mid指向的点是否为答案)
例如,在题目“Kth Largest 第k大的数”中 ==> λ∈[ a[1]b[1], a[n]b[n] ]
对于某个候选答案,如果“>λ的乘积个数"
例如,在题目“Drying 烘干衣服”中 ==>
需要寻找第一个出现的“能”(true),即如果check(mid-1)==false && check(mid)==true ,则答案为mid.
----------------------------------------------------------------------------------------------------------------------------------
二、举例
例一、Kth Largest 第k大的数
题目来源: http://acm.hrbeu.edu.cn/index.php?act=problem&id=1211
TimeLimit: 1 Second MemoryLimit: 32 Megabyte
Description
There are two sequences A and B with N (1<=N<=10000) elements each. All of the elements are positive integers. Given C=A*B, where '*' representing Cartesian product, c = a*b, where c belonging to C, a belonging to A and b belonging to B. Your job is to find the K'th largest element in C, where K begins with 1.
Input
Input file contains multiple test cases. The first line is the number of test cases. There are three lines in each test case. The first line of each case contains two integers N and K, then the second line represents elements in A and the following line represents elements in B. All integers are positive and no more than 10000. K may be more than 10000.
Output
For each case output the K'th largest number.
Sample Input
2
2 1
3 4
5 6
2 3
2 1
4 8
Sample Output
24
8
【题解】:直接二分答案,然后判断答案的正确性。
假设当前二分的答案为 t,那么:
对于ai <= t的衣服,显然让它们自然风干就可以了。
对于ai > t的衣服,我们需要知道该衣服最少用多少次烘干机。
设该衣服用了x1分钟风干,用了x2分钟烘干机。
那么有 x1 + x2 = t 和 ai <= x1 + x2 * k,联立两式可得 x2 >= (ai - t) / (k - 1),即最少使用次数为[(ai - t) / (k - 1)] 的最小上界。
最后,判断一下总使用次数是否少于 t 即可。
【题解二】双重二分。先对两个序列A,B从大到小排序,然后可以我们进行第一重二分:对要求取的答案二分,即求取的答案在[A[n]*B[n],A[1]*B[1]]之间,取s1=A[n]*B[n],e1=A[1]*B[1],mid=(s1+e1)/2,那么我们去计算在序列C中大于等于这个mid值的个数是多少,当然不是算出这个序列来,而是进行第二次二分。我们对于两个序列可以这样处理,枚举序列A,二分序列B,也就是说对于每个A[i],我们去二分序列B,来计算大于等于mid值的个数。那么我们可以得到结束条件,当大于等于mid值的个数大于等于k,且大于mid值的个数小于k,那么答案就是这个mid。那么时间复杂度就为n*log(n)*log(n^2)了。
代码:
#include
#include
#include
#include
#include
#include
#include
例二、POJ 3104 Drying 烘干衣服
题目来源: http://poj.org/problem?id=3104
代码:
#include
#include
using namespace std;
int n; //衣服件数
int water[100010]; //水分
int k; //烘干机一次烘掉的水分
int t=-1; //最终答案:所有衣服干完的最少时间
int maxT=-1; //所有衣服干完最长用时
//用时不超过mid (<=mid)能否烘干衣服, T=O(n)
bool check(int mid){
/*
若某件衣服 i水分water[i]= water[i]
∴ t2 >= (water[i]-mid)/(k-1)
只需要判断使用烘干机的时间和是否 <= mid 即可
*/
double radTime=0; //使用烘干机的总时间
for(int i=0;imid){
radTime += ceil( (double)(water[i]-mid)/(double)(k-1) ); //衣服 a[i]需要烘的次数
} //???1. ceil()函数
}
if(radTime<=mid)
return true;
else
return false;
}
//所有衣服干完最少时间:[0,maxT], 二分答案 , T=O(log(maxT))O(n)
void solve(){
int l=0;
int r=maxT;
int mid=(l+r)>>1; //mid是在“二分答案 ”过程中每次观察的答案
//???2. 在二分查找中总结这种变形的二分查找: false, false, false, ..., false, true, true, ..., true
// 找第一个出现的 true 的位置
while(l<=r){
//每次进入循环,总能保证最终答案 t∈[l,r]
if(check(mid)==true){
if(check(mid-1)==false){//再少用1个单位的时间都不能烘干
t=mid; //最终答案:最少用时mid
return;
}
r=mid-1;
}else{
l=mid+1;
}
mid=(l+r)>>1;
}
}
int main(void){
scanf("%d",&n); //???3. 看scanf()
//while(n>0){
//输入
int res=0;
for(int i=0;iwater[i]?maxT:water[i];
}
scanf("%d",&k);
//计算 & 输出
solve();
printf("%d\n",t);
//scanf("%d",&n);
//}
system("pause");
return 0;
}