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


    好久没写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
    ",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
    ",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
    ",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
    ",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
    ", 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
    ", 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
    ", 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
    ",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
    ",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
    ",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

  • 相关阅读:
    浏览器的垃圾回收机制
    vue-router传参数的方式
    Vue插槽
    自定义事件
    vue计算属性和监听器
    vue绑定样式
    循环中使用同步请求
    小白之路 | 从小学一年级期末考试看servlet+jsp技术
    Java实现简单计算器的探索性做法
    分布式数据库NoSQL简介
  • 原文地址:https://www.cnblogs.com/noobimp/p/10510066.html
Copyright © 2020-2023  润新知