二分搜索:
前些天,把二分搜索某些细节想复杂了。
①其实二分只要固定个人写法就没问题(整数题目一般取左端点left,与题目取最大值还是最小值无关)
②判断条件的大于小于等于号也没有那么复杂,特判一下等号和题目让你最大化还是最小化而已。
③难在判断条件函数绕了个弯,要自己建立另一个变量,使这个变量和二分搜索的量形成单调关系。
前缀和:sum数组,从1开始
最小值:res,min(res,xxx)
区间问题很容易先想到暴力,但暴力要i,j两个for,复杂度O(n^2),所以考虑优化,由于二分可以实现固定一个端点,移动另一个端点找。(二分只能改变一个变量)
以下是二分搜索代码,复杂度O(nlogn),141ms,主要是不是最优:
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;
int a[1000004];
int sum[1000005];
int n,s;
int main()
{
int T;
cin>>T;
while(T--)
{
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
scanf("%d%d",&n,&s);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];//前缀和
}
if(sum[n]<s)
cout<<"0"<<endl;
else
{
int res=1000000000;//取最小值套路
int l;
int r;
int mid;
int i;
for( i=1;sum[n]-sum[i]>=s;i++)//结束条件很关键,如果取不到和S,就不要往后面找了
{
l=i;
r=n;
while(l<=r)
{
mid=(l+r)/2;
if(sum[mid]-sum[i]<s)
l=mid+1;
else //就算等于,我还想找更短的
r=mid-1;
}
//cout<<l-i<<endl;
res=min(res,l-i);
}
cout<<res<<endl;
}
}
}
接下来是尺取法,O(n)复杂度,79ms!可以实现两个端点的移动,其实仔细想想,尺取法和二分搜索思路很像!
以本题找最小值为例:
满足条件 不满足条件
二分 缩小范围,r指针向左 扩大范围,l指针向右
尺取法 缩小范围,左指针右移 扩大范围,右指针右移
找最大值反之。
注意:
①区间求和和端点移动先后次序 !!!a[s++]和a[++s],以后莫队也会用到。
②关于区间问题,有些要注意临界端点的问题。(以后有再提)
代码如下:
#include<iostream>
#include<stdio.h>
using namespace std;
int a[10000005];
int n,s;
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&s);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
int l,r;
l=r=0;
int sum=0;
int res=1000000000;
for(;;)
{
while(sum<s&&r<n) //右端点左移
{
sum+=a[r];
r++; //区间移动和端点移动先后次序 !!!
}
if(sum<s)break;
res=min(res,r-l);
sum-=a[l]; //左端点右移,不用再写个while(sum>=s),多此一举
l++;
}
if(res>n)
cout<<"0"<<endl;
else
cout<<res<<endl;
}
}