二分总结:HDU 1551,4190;POJ 1905,3273,3122,3518;CF 371C

感觉:首先,先总结这两天做的二分题目。

因为根据这几个月以来做的CF还有组队赛,里面似有似无的存在着二分的影子,而二分以前还没有系统的做过,所以总是自己的弱项。再在终于狠下心来学习了。

学了两天,收获还是挺多的。

二分的用处太大了,不管是求简单的方程,还是求最优解方面都是不错的解题思想。

只要在线性,顺序或者有序的数据里就可以用二分来找最优的答案,而且时间平均都是O(log2 n)。题目中好像是HDU 4190吧,这题的限时是10000ms,而用二分做才用时1000ms,其优点可想而知。

不过就像《编程珠玑》中说的一样,虽然二分思路及其做法很爽,但是编写二分的程序总是错漏百出的。二分的第一个程序出现在1942年,但是直到1962年出出现了第一个没有bug的二分程序,其编写正确难度可想而知。

因为有一次做CF的时候有道题宝哥一看就知道是二分了,但是我后面看了好久都不知道是用的二分,所以就知道自己简单太弱了!这几天接连出现二分题目,所以不得不系统的学习一下了。

解题技巧:

(1)整形数据题目:l 为下界,r 为上界

一般的整形数据的题其循环都是 :

while ( l < r )   然后l=mid+1,high=mid 这各形式的;

或者有的题目边界要求比较强就得是  

while ( l <= r) 然后 l=mid+1,r=mid-1 这各形式;

还有道题就是CF 371C那道中的边界处理要求比较高就是:

while(  l+1 < r ) 然后 l=mid,r=mid


(2)浮点型题目: #define eps 1e-5

一般浮点型题目都会与精度打交道,所以势必与eps有关,因为如果如果精度要求0.01,那么如果你在 l=mid+eps这样做的话,这里我设eps为0.00001,那么时间复杂度就会乘以10^3了,那么既然二分是减少时间的,这样又会增加时间复杂度,那该怎么避免这个problem呢。

所以在HDU 1551这题上我就掉进了这个坑了,我把精度写在 l=mid+eps里了,然后直接TLE。  我把精度写在while里面的时候时间直接下降很多。因为每次都是平分,这就与eps没多大关系了,只要能接近最优答案就行。所以技巧如下:

while( r - l >eps)  然后 l=mid , r=mid;即可。

解题的基本思路就是:

while( l < r )
{
	mid=(l+r)/2; //如果是整数用移位>>1更加快
	if(gao(mid)<=m) l=mid+1;  //gao函数是处理二分枚举之后验证最佳答案是否符合的函数
	else r=mid; 
}


下面是这些题的代码及分析:

POJ 1905

题意:就是求棍弯曲后中点与原来的中点的距离值。

感觉:好久不做二分了,以前做的只是简单的二分,没有做过数学或者几何的二分,北邮二分题都不知道是用的二分……唉……可怜凄凄啊!!!前学好二分再说了……

思路:分析:http://blog.csdn.net/lyy289065406/article/details/6648562

看了队友教大一的PPT,哈哈,上次做了一个精度的题目,所以二分数学答案精度也是有很大的关系,二分如果精度做得不好,就得不到想要得到的答案。还是得好好学啊……

#include <iostream>  
#include <cstdio>  
#include <fstream>  
#include <algorithm>  
#include <cmath>  
#include <deque>  
#include <vector>  
#include <list>  
#include <queue>  
#include <string>  
#include <cstring>  
#include <map>  
#include <stack>  
#include <set>  
#define PI acos(-1.0)  
#define eps 1e-5  
#define mem(a,b) memset(a,b,sizeof(a))  
#define sca(a) scanf("%d",&a)  
#define pri(a) printf("%d\n",a)  
#define MM 2000005  
#define MN 1005  
#define INF 10000007  
using namespace std;  
typedef long long ll;  
int main()  
{  
    double l,n,c,s,r,low,high,mid;  
    while(cin>>l>>n>>c)  
    {  
        if(l<0&&n<0&&c<0) break;  
        low=0,high=0.5*l;  
        s=(1+n*c)*l;  
        while(high-low>eps)  
        {  
            mid=(low+high)/2;  
            r=(4*mid*mid+l*l)/(8*mid);  
            if(2*r*asin(l/(2*r))<s) low=mid;  
            else high=mid;  
        }  
        printf("%.3f\n",mid);  
    }  
    return 0;  
} 

POJ 3273

题意:就是给出n个数,然后分成m组,要求分得各组的花费之和应该尽可能地小,最后输出各组花费之和中的最大值。

感觉:二分可以求方程的解,这个我承认。不过这题确实有点神哦……这样也能二分,太神了,难怪以前做CF的时候遇到好多这样的题,然后都是用暴力搞不出来,听宝哥说都是用的二分,原来如此,越学越觉得二分用得真爽啊……

题意:二分枚举最佳的最大值,然后用最大值去枚举这n个数能分成的组数,逐渐逼近最优答案即可。

#include <iostream>  
#include <cstdio>  
#include <fstream>  
#include <algorithm>  
#include <cmath>  
#include <deque>  
#include <vector>  
#include <list>  
#include <queue>  
#include <string>  
#include <cstring>  
#include <map>  
#include <stack>  
#include <set>  
#define PI acos(-1.0)  
#define eps 1e-5  
#define mem(a,b) memset(a,b,sizeof(a))  
#define sca(a) scanf("%d",&a)  
#define pri(a) printf("%d\n",a)  
#define MM 100005  
#define MN 1005  
#define INF 10000007  
using namespace std;  
typedef long long ll;  
int n,m,a[MM],l,r,mid;  
int gao(int mid)  
{  
    int i,sum=0,group=1;  
    for(i=0;i<n;i++)  
        if(sum+a[i]<=mid) sum+=a[i];  
        else sum=a[i],group++;  
    if(group>m) return 1;  //mid偏小  
    return 0; //mid偏大  
}  
int main()  
{  
    scanf("%d%d",&n,&m);  
    for(int i=0;i<n;i++)  
    {  
        sca(a[i]);  
        r+=a[i];  
        l=max(l,a[i]);  
    }  
    while(l<=r)  
    {  
        mid=(l+r)/2;  
        if(gao(mid)) l=mid+1;  
        else r=mid-1;  
    }  
    pri(mid);  
    return 0;  
}

POJ 3122

题意:就是求最大的体积能每个人得到的都相同的,并且切出来的这个不能是由小的加出来的,必须是由大的切出来得到的最大值。

思路:做了前两道题,都是参考了别人的代码,现在终于自己能写二分了,虽然在处理精度的时候还不是很会,不过慢慢来就会了,第一次写的这个二分有点挫了,一点精度一失就不得答案了,真郁闷……就是寻找最大的半径,刚开始是找最大的半径的,不过精度损失严重,然后直接求体积再二分了。得到最大的体积,然后用所有的体积除这个二分的体积,然后在f+q附近就是二分的最优答案了

#include <iostream>  
#include <cstdio>  
#include <fstream>  
#include <algorithm>  
#include <cmath>  
#include <deque>  
#include <vector>  
#include <list>  
#include <queue>  
#include <string>  
#include <cstring>  
#include <map>  
#include <stack>  
#include <set>  
#define PI acos(-1.0)  
#define eps 1e-6  
#define mem(a,b) memset(a,b,sizeof(a))  
#define sca(a) scanf("%d",&a)  
#define pri(a) printf("%d\n",a)  
#define MM 100005  
#define MN 1005  
#define INF 10000007  
using namespace std;  
typedef long long ll;  
int n,m;  
double a[MM];  
int gao(double mid)  
{  
    int sum=0,i;  
    for(i=0;i<n;i++)  
        sum+=int(a[i]/mid);  
    if(sum<m) return 0;  
    return 1;  
}  
int main()  
{  
    int t;  
    sca(t);  
    while(t--)  
    {  
        scanf("%d%d",&n,&m);  
        m++;  
        double l,r,mid;  
        for(int i=0; i<n; i++)  
        {  
            scanf("%lf",&a[i]);  
            a[i]*=a[i]*PI;  
            if(r<a[i]) r=a[i];  
        }  
        l=0;  
        while(r-l>eps)  
        {  
            mid=(l+r)/2;  
            if(gao(mid)) l=mid;  
            else r=mid;  
        }  
        printf("%.4f\n",mid);  
    }  
    return 0;  
} 

POJ 3518

题意:就是求这个数两边的素数之差,如果是素数了的话就直接输出0.

思路:先打表再二分答案。

这题搞了好久才过,晕……刚开始找素数表时,数组开太大了不行,开太小也不行,然后哈哈学别人用char数组就可以了,以前还没这么用过……又学到了点有用的东东……

#include <iostream>  
#include <cstdio>  
#include <fstream>  
#include <algorithm>  
#include <cmath>  
#include <deque>  
#include <vector>  
#include <list>  
#include <queue>  
#include <string>  
#include <cstring>  
#include <map>  
#include <stack>  
#include <set>  
#define PI acos(-1.0)  
#define eps 1e-6  
#define mem(a,b) memset(a,b,sizeof(a))  
#define sca(a) scanf("%d",&a)  
#define pri(a) printf("%d\n",a)  
#define MM 100005  
#define MN 1005  
#define INF 10000007  
using namespace std;  
typedef long long ll;  
int prime[MM],cnt,k,low,high;  
char is[1299719];  
void is_prime()  
{  
    int i,j,k=1299709;  
    for(i=2;i<=k;i++)  
        if(!is[i])  
        {  
            prime[cnt++]=i;  
            for(j=i+i;j<=k;j+=i) is[j]=1;  
        }  
}  
void gao()  
{  
    int i,j,l=0,r=cnt,mid;  
    while(l<r)  
    {  
        mid=(l+r)>>1;  
        if(prime[mid]>k) r=mid;  
        else l=mid+1;  
    }  
    if(prime[l]>k) high=l,low=l-1;  
    else high=l+1,low=l;  
}  
int main()  
{  
    is_prime();  
    while(sca(k)&&k)  
    {  
        if(!is[k]) puts("0");  
        else  
        {  
            gao();  
            pri(prime[high]-prime[low]);  
        }  
    }  
    return 0;  
}  

CF 371C

C. Hamburgers
time limit per test
 1 second
memory limit per test
 256 megabytes
input
 standard input
output
 standard output

Polycarpus loves hamburgers very much. He especially adores the hamburgers he makes with his own hands. Polycarpus thinks that there are only three decent ingredients to make hamburgers from: a bread, sausage and cheese. He writes down the recipe of his favorite "Le Hamburger de Polycarpus" as a string of letters 'B' (bread), 'S' (sausage) и 'C' (cheese). The ingredients in the recipe go from bottom to top, for example, recipe "ВSCBS" represents the hamburger where the ingredients go from bottom to top as bread, sausage, cheese, bread and sausage again.

Polycarpus has nb pieces of bread, ns pieces of sausage and nc pieces of cheese in the kitchen. Besides, the shop nearby has all three ingredients, the prices are pb rubles for a piece of bread, ps for a piece of sausage and pc for a piece of cheese.

Polycarpus has r rubles and he is ready to shop on them. What maximum number of hamburgers can he cook? You can assume that Polycarpus cannot break or slice any of the pieces of bread, sausage or cheese. Besides, the shop has an unlimited number of pieces of each ingredient.

Input

The first line of the input contains a non-empty string that describes the recipe of "Le Hamburger de Polycarpus". The length of the string doesn't exceed 100, the string contains only letters 'B' (uppercase English B), 'S' (uppercase English S) and 'C' (uppercase English C).

The second line contains three integers nbnsnc (1 ≤ nb, ns, nc ≤ 100) — the number of the pieces of bread, sausage and cheese on Polycarpus' kitchen. The third line contains three integers pbpspc (1 ≤ pb, ps, pc ≤ 100) — the price of one piece of bread, sausage and cheese in the shop. Finally, the fourth line contains integer r (1 ≤ r ≤ 1012) — the number of rubles Polycarpus has.

Please, do not write the %lld specifier to read or write 64-bit integers in С++. It is preferred to use the cincout streams or the %I64dspecifier.

Output

Print the maximum number of hamburgers Polycarpus can make. If he can't make any hamburger, print 0.

Sample test(s)
input
BBBSSC
6 4 1
1 2 3
4
output
2
input
BBC
1 10 1
1 10 1
21
output
7
input
BSC
1 1 1
1 1 3
1000000000000
output
200000000001

思路:直接枚举答案,然后计算出他们的价值,如果大于给的钱上界就等于mid,否则下界等于mid,如此便可找出最佳答案……

这样的题可以二分,以前竟然不知道,唉……确实如魏神所说,做题还是太少了呀!!!以前也没有用二分做这些题,再在终于知道二分的用处有多大了,只要类似这种题都可以直接用二分暴力找出最佳答案的。

不过在上下界的判断语句上还不是很熟……前闭后开,前闭后闭的二分还不是分得很清楚,所以这题while中是 l+1<r 才得过,写成 l<r 就不行,无语,在下面 l=mid+1 了也不行,唉……二分就是这点还做不好了,多做几道练练吧!

#include <iostream>  
#include <cstdio>  
#include <fstream>  
#include <algorithm>  
#include <cmath>  
#include <deque>  
#include <vector>  
#include <list>  
#include <queue>  
#include <string>  
#include <cstring>  
#include <map>  
#include <stack>  
#include <set>  
#define PI acos(-1.0)  
#define eps 1e13  
#define mem(a,b) memset(a,b,sizeof(a))  
#define sca(a) scanf("%d",&a)  
#define pri(a) printf("%d\n",a)  
#define MM 100005  
#define MN 1005  
#define INF 10000007  
using namespace std;  
typedef long long ll;  
ll ss[4],z[4],zz[4];  
int main()  
{  
    char s[102];  
    ll i,p,l=0,r=eps,mid,cost;  
    scanf("%s",s);  
    for(i=0;i<strlen(s);i++)  
    {  
        if(s[i]=='B') ss[0]++;  
        else if(s[i]=='S') ss[1]++;  
        else ss[2]++;  
    }  
    cin>>z[0]>>z[1]>>z[2]>>zz[0]>>zz[1]>>zz[2]>>p;  
    while(l+1<r)  
    {  
        mid=(l+r)>>1;  
        cost=0;  
        for(i=0;i<3;i++)  
            if(mid*ss[i]>z[i]) cost+=(mid*ss[i]-z[i])*zz[i];  
        if(cost<=p) l=mid;  
        else r=mid;  
    }  
    cout<<l<<endl;  
    return 0;  
}  

HDU 4190

挺简单的一道二分题……直接枚举最佳人数就行了,然后二分找……

不过要注意进位,因为如果只要小数中有一位不为0,那就得进位了……就是gao函数里面滴……小数与整数比较看看小数部分有没有不为0的数,有则进位。

#include <iostream>  
#include <cstdio>  
#include <fstream>  
#include <algorithm>  
#include <cmath>  
#include <deque>  
#include <vector>  
#include <list>  
#include <queue>  
#include <string>  
#include <cstring>  
#include <map>  
#include <stack>  
#include <set>  
#define PI acos(-1.0)  
#define mem(a,b) memset(a,b,sizeof(a))  
#define sca(a) scanf("%d",&a)  
#define pri(a) printf("%d\n",a)  
#define lson i<<1,l,mid  
#define rson i<<1|1,mid+1,r  
#define MM 500005  
#define MN 3005  
#define INF 10000007  
#define eps 1e13  
using namespace std;  
typedef long long ll;  
int a[MM],n,m;  
int gao(int mid)  
{  
    int i,sum=0;  
    for(i=0;i<n;i++)  
    {  
        double b=(a[i]*1.0)/(mid*1.0);  
        int c=a[i]/mid;  
        if(b>(double)c) sum+=c+1;  //注意进位即可  
        else sum+=c;  
    }  
    return sum;  
}  
int main()  
{  
    while(scanf("%d%d",&n,&m)&&n!=-1&&m!=-1)  
    {  
        int i,l=1,r=-INF,mid;  
        for(i=0;i<n;i++)  
            sca(a[i]),r=max(r,a[i]);  
        if(n==m) {pri(r);continue;} //尼玛,加了这句话反而加时15ms了  
        while(l<r)  
        {  
            mid=(l+r)>>1;  
            if(gao(mid)<=m) r=mid;  
            else l=mid+1;  
        }  
        pri(l);  
    }  
    return 0;  
}  

HDU 1551

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <list>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define PI acos(-1.0)
#define mem(a,b) memset(a,b,sizeof(a))
#define sca(a) scanf("%d",&a)
#define pri(a) printf("%d\n",a)
#define lson i<<1,l,mid
#define rson i<<1|1,mid+1,r
#define MM 10005
#define MN 3005
#define INF 10000007
#define eps 1e-7
using namespace std;
typedef long long ll;
double a[MM];
int n,m;
int gao(double mid)
{
    int i,sum=0,b;
    for(i=0;i<n;i++)
        b=a[i]/mid,sum+=b;
    return sum;
}
int main()
{
    while(scanf("%d%d",&n,&m)&&(n||m))
    {
        int i;
        double l=0,r=-INF,mid;
        for(i=0;i<n;i++)
        {
            scanf("%lf",&a[i]);
            if(r<a[i]) r=a[i];
        }
        while(r-l>eps) //放在这的话,就免去了10^3的复杂度了
        {
            mid=(l+r)/2;
            if(gao(mid)<m) r=mid;
            else l=mid; //刚开始是在这里加上eps的,但是这样超时了
        }
        printf("%.2f\n",l>=1.0?l:0);
    }
    return 0;
}


你可能感兴趣的:(二分总结:HDU 1551,4190;POJ 1905,3273,3122,3518;CF 371C)