……

二分、三分专题——经典题型略解

好久没写blog了嗷。

挑战程序设计竞赛上二分的标题就是——不光是查找值。所以在这里总结一下上一周的二分三分训练。

零、有序数组中查找某个值(不说了)

 

一、最大化最小值

POJ2456、POJ3258 这两个题非常像嗷,稍微改下代码就可以了嗷。

我先做的3258.

2456:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 using namespace std;
 5 #define maxn 50050
 6 int l,n,m;
 7 int dis[maxn];
 8 
 9 bool C(int x){
10     int last=0;
11     for(int i=1;i<m;i++){//m-1次
12         int cur=last+1;
13         while(cur<n&&dis[cur]-dis[last]<x)
14             cur++;
15         if(cur==n) return 0;
16 
17         last=cur;
18     }
19     return 1;
20 }
21 int main(){
22     scanf("%d%d",&n,&m);
23     for(int i=0;i<n;i++) scanf("%d",dis+i);
24     sort(dis,dis+n);
25 
26     int le=0,ri=dis[n-1];
27     while(ri-le>1){
28         int mid=(ri+le)/2;
29 
30         if(C(mid)) le=mid;//如果放得开,说明x还可以更大
31         else ri=mid;
32     }
33     printf("%d\n",le);
34     return 0;
35 }

 

3258:

 1 /*
 2 一条长L的河上,除了0 和 l 处还有N 个石子,分别距离起点距离di,
 3 求去掉M个石子后相邻的最小距离的最大值。(最大化最小值)
 4 和poj2456基本一样 2456是给每个点的位置,这个题还要加上一个从0开始,到l处结束,相当于多两个元素
 5 
 6 定义C(x):可以去掉m个石头使任意石头间距不小于x。
 7 */
 8 
 9 #include <iostream>
10 #include <cstdio>
11 #include <algorithm>
12 using namespace std;
13 #define maxn 50050
14 int l,n,m;
15 int dis[maxn];
16 
17 bool C(int x){
18     int num=n-m;
19     int last=0;             //删除num个石头,循环num次 但2456里选择m个点只需循环m-1次,一开始想不明白wa了很久
20     for(int i=0;i<num;i++){//对于这些石头,要使任意间距不小于x,
21         int cur=last+1;
22         while(cur<=n&&dis[cur]-dis[last]<x)//就要把下一个放入第一个不满足while条件的位置
23             cur++;                        //由cur记录
24         if(cur>n) return 0;//如果在这个过程中大于n了,说明放不开
25 
26         last=cur;//更新检查完的位置
27     }
28     return 1;
29 }
30 int main(){
31     scanf("%d%d%d",&l,&n,&m);
32     if(n==m) {
33         printf("%d\n",l);
34         return 0;
35     }
36     for(int i=1;i<=n;i++) scanf("%d",dis+i);
37     dis[n+1]=l;
38     sort(dis,dis+n+2);
39 
40     int le=0,ri=l;
41     while(ri-le>1){
42         int mid=(ri+le)/2;
43 
44         if(C(mid)) le=mid;//如果放得开,说明x还可以更大
45         else ri=mid;
46     }
47     printf("%d\n",le);
48     return 0;
49 }

 

二、假定一个解判断是否可行/是否满足条件

POJ1759、POJ3104、POJ1064(坑)

其实1759看网上别人的blog还有依次算、依次满足的方法,在这个题里也很快。

1759二分:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 #define inf 0x3f3f3f3f
 5 #define maxn 1006
 6 #define eps 1e-8//
 7 int n;
 8 double A,num[maxn];
 9 bool solve(double mid){
10     num[1]=mid;
11     for(int i=2;i<n;i++){
12         num[i]=2*num[i-1]+2-num[i-2];
13         if(num[i]<eps) return false; //写小于0由于精度问题会wa
14     }
15     return true;
16 }
17 int main(){
18     while(scanf("%d%lf",&n,&A)==2){
19         num[0]=A;
20         double low=-inf;
21         double high=inf;
22         for(int i=0;i<100;i++){
23             double mid=(low+high)/2;
24             if(solve(mid)) high=mid;
25             else low=mid;
26         }
27         printf("%.2lf\n",num[n-1]);
28     }
29     return 0;
30 }

 

1759另解:

 1 /*
 2 H1 = a, H3 = H2 * 2 + 2 - H1, H4 = H3 * 2 + 2 - H2, .....
 3 Hn = Hn-1 * 2 + 2 - Hn-2
 4 每个式子都能化简为k * H2 + b的形式
 5 */
 6 #include <bits/stdc++.h>
 7 using namespace std;
 8 #define maxn 1005
 9 
10 int n,k[maxn];
11 double a,b[maxn],h2;
12 double solve(){
13     double h2=0;
14     k[1]=0, b[1]=a, k[2]=1, b[2]=0.0;//H1=0 * H2 + a, H2=1 * H2 + 0
15     for (int i = 3; i <= n; i ++) {
16         k[i] = 2 * k [i - 1] - k[i - 2];
17         b[i] = 2 * b[i - 1] - b[i - 2] + 2;
18         if (h2 * k[i] + b[i] < 0)
19             h2 = -b[i] / k[i];//令此式==0解出h2, 可使h2尽量小, 最后乘出来的hn也就最小
20     }
21     return h2*k[n]+b[n];
22 }
23 int main() {
24     while (~scanf("%d%lf", &n, &a)) {
25         printf("%.2lf\n", solve());
26     }
27     return 0;
28 }

 

3104:

 1 /*
 2 1、对于一件ai值小于等于mid的衣服,直接晾干即可;
 3 2、对于一件ai值大于mid值的衣服,最少的用时是用机器一段时间,晾干一段时间
 4 C(mid):所有衣物都能干的最短时间mid
 5 设这两段时间分别是x1和x2,那么有mid=x1+x2,ai<=k*x1+x2,
 6 解得x1>=(ai-mid)/(k-1) ,所以对(ai-mid)/(k-1)向上取整就是该件衣服的最少用时。
 7 */
 8 //#include<bits/stdc++.h>
 9 #include <iostream>
10 #include <cstdio>
11 #include <cmath>
12 using namespace std;
13 #define ll long long
14 long long a[100005];//用int会WA
15 int n;
16 ll k;
17 bool C(ll x){
18     ll sum = 0;
19     for (int i = 0; i < n; i++)
20         if (a[i] > x)
21             sum += ceil ( (a[i] - x) * 1.0 / (k - 1));//所有衣服晾干需要的总时间
22 
23     return sum>x;
24 }
25 
26 int main() {
27     scanf ("%d", &n);
28     ll  maxx = -1;
29     for (int i = 0; i < n; i++) {
30         scanf ("%lld", a+i);
31         maxx=max(maxx,a[i]);// 找出含水量的最大值作为上界
32     }
33     scanf ("%lld", &k);
34     if (k == 1)//k-1作除数会re
35         printf ("%lld\n", maxx);
36     else {
37         ll left = 1, right = maxx, mid;
38         while (right > left) {
39             mid = (left + right) / 2;
40 
41             if (C(mid)) left = mid+1;
42             else right= mid;
43         }
44         printf ("%lld\n", left);
45     }
46     return 0;
47 }

 

三、最大化平均值

POJ2976

 2976:

 1 ///C(x):a[i]-x*b[i]从大到小排列前n-k个的和大于等于零
 2 //#include <bits/stdc++.h>
 3 #include <iostream>
 4 #include <cstdio>
 5 #include <algorithm>
 6 using namespace std;
 7 #define maxn 1005
 8 int n,k,a[maxn],b[maxn];
 9 double y[maxn];//a[i]-x*b[i]
10 bool C(double x){
11     for(int i=0;i<n;i++) y[i]=a[i]-x*b[i];
12     sort(y,y+n);
13 
14     double sum=0;
15     for(int i=0;i<n-k;i++) sum+=y[n-i-1];
16 
17     return sum>=0;
18 }
19 int main(){
20     while(scanf("%d%d",&n,&k)&&(n||k)){
21         for(int i=0;i<n;i++) scanf("%d",a+i);
22         for(int i=0;i<n;i++) scanf("%d",b+i);
23 
24         double l=0,r=1000;
25         for(int i=0;i<100;i++){
26             double mid=(l+r)/2;
27             if(C(mid)) l=mid;
28             else r=mid;
29         }
30         printf("%.0lf\n",100.0*l);//四舍五入
31     }
32     return 0;
33 }

 

 

四、导数求值

HDU2899、HDU1724

2899:

 1 #include <iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 const double eps = 1e-8;
 5 using namespace std;
 6 double hs(double x,double y){//原函数
 7     return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-y*x;
 8 }
 9 double ds(double x,double y){//导函数
10     return 42*pow(x,6)+48*pow(x,5)+21*pow(x,2)+10*pow(x,1)-y;
11 }
12 int main(){
13     int a;
14     scanf("%d",&a);
15     while(a--){
16         double b,l,r,mid;
17         scanf("%lf",&b);
18         l=0.0; r=100.0;
19         while(r-l>eps){
20             mid=(l+r)/2;
21             if(ds(mid,b)>0) r=mid;
22             else l=mid;
23         }
24         printf("%.4lf\n",hs(l,b));
25     }
26     return 0;
27 }

 

 

1724:用到了辛普森公式,建议补充数学知识。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define ll long long
 5 const double eps = 1e-10;//1e-8 WA了 
 6 double a, b;
 7 double f (double x) {
 8     return b * sqrt (1.0 - (x * x) / (a * a));
 9 }
10 double simpson (double l, double r) {
11     return (f (l) + 4.0 * f ( (l + r) / 2.0) + f (r)) / 6.0 * (r - l);
12 }
13 double integral (double l, double r) {
14     double mid = (l + r) / 2.0;
15     double res = simpson (l, r);
16     if (fabs (res - simpson (l, mid) - simpson (mid, r)) < eps) return res;
17     else return integral (l, mid) + integral (mid, r);
18 }
19 
20 int main() {
21     int T; scanf("%d",&T);
22     double l, r;
23     while (T--) {
24         scanf("%lf%lf%lf%lf",&a,&b,&l,&r);
25         printf("%.3lf\n",2 * integral (l, r));
26     }
27     return 0;
28 }

 

五、三分(挖坑)

 二分要解决的问题是不严格单调序列,但是遇到如单峰函数或单谷函数这样的情况,就要用三分了。

以找单峰函数f(x)最大值为例:

 1 double fun(double x){
 2     //f(x)
 3 }
 4 
 5 double tri_search(double left, double right){
 6     double midl, midr;
 7     while (right-left > eps){
 8         midl = (left + right) / 2.0;
 9         midr = (midl + right) / 2.0;
10         // 如果是求最小值的话这里判<=即可
11         if(fun(midl) >= fun(midr)) right = midr;
12         else left = midl;
13     }
14     return left;
15 }

不过这只是最简单的形式,一般题目里还是要写一个判断条件的函数,再推一个凸函数或凹函数公式,然后依据这个条件对这个函数来做三分。

POJ3296、POJ3737

详细看人家写的嗷:https://blog.csdn.net/pi9nc/article/details/9666627

posted @ 2019-03-11 13:29  noobimp  阅读(461)  评论(0编辑  收藏  举报