您的位置:首页 > 大数据 > 人工智能

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

2019-01-17 00:42 344 查看

本文同步发布于个人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{

4000
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;
}

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: