【尺取+二分+三分总结】2019 GDUT Winter Training I

本文同步发布于个人blog,点此访问
寒假集训专题一(对没错本蒟蒻来CtrlCV大法写题解+总结了)
专题一(点这里)包括尺取,二分,三分,dfs,bfs(搜索好难_(:з」∠)_)
这篇文字就简单讲讲尺取、二分、三分吧


尺取

基本思路
尺取也被称为“毛毛虫法”,顾名思义,在求一段连续子区间时,左右边界进行挪动从而得到解。
这不是废话嘛谁知道你在讲什么)那么接下来从例子入手。

例题一(A题):大意是给一段n个正整数的序列,让你找出最短的满足区间和大于S的子序列的长度

对于这道题,我们可以考虑维护l、r表示当前区间的边界,初始l,r指向序列头,对于当前区间,
如果区间和仍小于S,那么我们需要使r往前移(就像毛毛虫的头往前挪),直到当前区间和大于
等于S,记录下当前区间长度这时候如果r继续前移,那么虽然当前区间满足条件,但长度必定比
已经记录的值更长,所以我们需要l前移(毛毛虫的尾巴)。重复上述过程,直到“毛毛虫”爬完整个序列。

代码如下

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;

const int maxn = 1e5+10;
int T,n,s,num[maxn];

int main() {
    scanf("%d",&T);
    while(T--) {
        scanf("%d %d",&n,&s);
        for(int i=1;i<=n;i++) {
            scanf("%d",&num[i]);
        }
        int l=1,r=1,sum=num[1],ans=maxn;
        while(l<=r&&r<=n) {
            //cout<<sum<<endl;
            if(sum>=s) {
                if(sum-num[l]>=s) sum-=num[l++];
                else{
                    //cout<<l<<" "<<r<<endl;
                    ans=min(r-l+1,ans);
                    sum-=num[l++];
                }
            }else{
                sum+=num[++r];
            }
        }
        if(ans==maxn) cout<<0<<endl;
        else cout<<ans<<endl;
    }
    return 0;
}

例题二(B题):大意是求1-n中哪一段子区间的平方和等于S。按字典序输出全部情况。

基本思路和例题一类似,唯一不同的是需要输出序列,可以考虑用数组记录下l,r,输出答案
时再输出即可。

那就直接贴代码了(只是懒得再写一遍题解):

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;

typedef long long ll;
const int maxn = 1e7;
ll n,x[maxn],y[maxn],ans=0;

int main() {
    scanf("%lld",&n);
    ll l=1,r=1,sum=1,len=sqrt(n)+2;
    while(l<=r&&r<=len) {
        //cout<<sum<<endl;
        if(sum>=n) {
                if(sum==n) {
                    ++ans;
                    x[ans]=l,y[ans]=r;
                }
                sum-=l*l;
                l++;
        }else{
            r++;
            sum+=r*r;
        }
    }
    cout<<ans<<endl;
    for(int i=1;i<=ans;i++) {
        cout<<y[i]-x[i]+1<<" ";
        for(int j=x[i];j<=y[i];j++) {
            cout<<j<<" ";
        }
        cout<<endl;
    }
    
    return 0;
}

小结:尺取可以在O(n)的时间得出类似的求一段连续子区间的解,虽然
适用性不广(至少在我看来,也可能是因为我菜QAQ),不过该用上的时候也会
有不错的效果。


二分

基本思路:二分的思想最早可以从中学学过的牛顿迭代求根开始接触到,
简单来说,假如要在一段单调性唯一(假定是单增)的序列中找到一个值,维护区间边界l,r,
每次取l,r中点值与需要找的值作比较,若过大,则右边界左移至中点处,反之则左边界右移
到中点处,如此往复,那么在每次折半后,最多只需要log2(n)次便可以检索到解。

下面三道例题:

例题三(C题):大意是给出n个位置,选出其中m个位置,使得m个位置之间的最短距离
最大。输出最大的最短距离。

这道题可以对距离进行二分,首先对位置排序,对于每个距离,如果能满足可以选择的位置
大于等于m个,那么该距离为合法距离。考虑维护区间边界l,r,中点mid,如果mid为合法距离
那么区间向右移,如果mid不合法,那么区间应该往左移才能得到合法距离。

具体看代码叭:

#include <cstdio>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1e9+5;
int num[100000+5],n,c;

int check(int o) {
    int u=1,now=1;
    for(int i=2;i<=n;i++) {
        while(i<=n&&num[i]-num[now]<o) {
            i++;
        }
        if(i<=n) {
            now=i;
            u++;
        }
    }
    if(u>=c) {
        return 1;
    }else{
        return 0;
    }
}

int main() {
    scanf("%d %d",&n,&c);
    for(int i=1;i<=n;i++) {
        scanf("%d",&num[i]);
    }
    sort(num+1,num+n+1);
    
    int l=0,r=maxn;
    while(l<r-1) {
        int mid=(l+r)/2;
        //cout<<l<<" "<<mid<<" "<<r<<endl;
        if(check(mid)) {
            l=mid;
        }else{
            r=mid;
        }
    }
    if(check(l))printf("%d\n",l);
    else printf("%d\n",r);
    return 0;
}

例题四(D题):一根棍子横立与两面墙之间,当棍子受热膨胀后会弯成弓形(可以看做
圆弧的一部分),求弯曲的棍子与原棍子的中点的距离。

题目思路不难想,设长度为x,那么根据勾股定理和三角函数就可以列出一系列方程,
那么只要对x进行二分,也就是前文提到的牛顿迭代求解的过程,就可以得到解。
(然而这道题我交了30发的WA,直到我把评测姬从g++改成c++,wa的一声哭出来)

代码如下:

#include <cstdio>
#include <string.h>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;

#define eps 1e-6
double L,LL,n,c;

int main() {
    while(~scanf("%lf %lf %lf",&L,&n,&c)) {
        if(L<0&&n<0&&c<0) break;
        
        LL=L*(1.0+n*c);
        double R,l=0.0,r=L*0.5,mid;
        while(r-l>eps) {
            mid=(l+r)/2.0;
            if(2 * asin((L / 2) / ((L*L + 4 * mid*mid) / (8 * mid)))*((L*L + 4 * mid*mid) / (8 * mid))>=LL) {
                r=mid;
            }else{
                l=mid;
            }
        }
        printf("%.3lf\n",mid);
    }
    return 0;
}

例题五(F题):简单二分+交互,具体看原题

CF的特有题,交互很有意思一般也不难,加上fflush(stdout)或者用
endl就问题不大了,没看清题可能会吃亏,(手动打码)我才不会告诉你这道题
我看错了大于小于号WA了近十发。(专题感觉像划水一样随随便便就交题了太不认真了orz)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int maxn = 1e9;

int main() {
    int l=0,r=1e9,mid;
    while(1) {
        mid=(l+r)/2;
        cout<<"Q "<<mid<<endl;
        
        string c;
        cin>>c;
        cout.flush();
        
        if(c=="=") exit(0);
        else if(c=="<") {
            r=mid;
        }else {
            l=mid+1;
        }
    }
    return 0;
}

小结:二分作为一个常用算法,写起来还不是特别顺手,以后打cf听师兄
说中间大部分都是二分题啊。


三分

基本思路:三分的思路和二分基本相似,不一样的是,二分求解的是单调序列的问题,而三分
求解的是一段有极值的序列。三分,顾名思义,在二分一个mid的基础上,取mid和r的中点为
mid2,通过对mid和mid2对应的值的比较,确定是否左右移,从而求解。

例题六(E题):大意是给出一系列二次函数,取F(x)=max(s[i](x)),x∈[1,n].求F(x)
的最小值,x属于[0,MAXN].

简单三分,话不多说,直接上代码叭

代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <cmath>
using namespace std;

const int maxn=1e4+5;
int T,n,a[maxn],b[maxn],c[maxn];

int main() {
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) {
            scanf("%d %d %d",&a[i],&b[i],&c[i]);
        }
        
        double l=0,r=1000,mid1,mid2,now1,now2;
        while(r-l>0.00000000001) {
            mid1=(l+r)/2;
            mid2=(mid1+r)/2;
            now1=a[1]*mid1*mid1+b[1]*mid1+c[1];
            now2=a[1]*mid2*mid2+b[1]*mid2+c[1];
            for(int i=2;i<=n;i++) {
                now1=max(now1,a[i]*mid1*mid1+b[i]*mid1+c[i]);
                now2=max(now2,a[i]*mid2*mid2+b[i]*mid2+c[i]);
            }
            
            //printf("%lf-%lf:%lf=%lf %lf=%lf\n",l,r,mid1,now1,mid2,now2);
            
            if(now1>now2) {
                l=mid1;
            }else{
                r=mid2;
            }
        }
        printf("%.4lf\n",now1);
    }
    return 0;
} 

后记:(第一篇总结报告写得像翔一样我也没有办法鸭_(:з」∠)_

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值