在符合一定条件的区间问题中,我们可以利用尺取法降低一维的复杂度。具体直接看例题有解释。当年我自己推出了尺取法,现在知道有这种方法却不会做题了。
例题
POJ 3061 Subsequence
给出一串数字,求其中总和大于等于s的区间长度的最小值。
利用前缀和暴力枚举前后区间,肯定会T.考虑到数字全部不小于0,可以用二分的方法找出使从位置i开始最小的能使和大于等于s的位置t,复杂度nlogn.
但是还有更简单的办法。固定位置l,r,每次不断将r向右边移动,把和sum加上a[r],直到sum大于s,判断此时r-l区间长度,求一下最小值,然后将l向右移动一位,重复上述步骤,直到r移到最右边并且sum还小于s退出循环。可以归纳证明对于每一个l来讲r都是从位置l开始最小的能使和大于等于s的位置,故该方法是正确的。由于l,r从1-n,故该方法的复杂度为2*n,即复杂度为O(n).
#include<algorithm>
#include<stdio.h>
using namespace std;
const int boss=1e5;
int t,n,s,ans,i,a[boss+10],sum;
int main()
{
for (scanf("%d",&t);t--;s=0)
{
for (scanf("%d%d",&n,&sum),i=1;i<=n;i++) scanf("%d",a+i);
int l=1,r=1;
for (ans=0x3f3f3f3f;;)
{
for (;r<=n&&s<sum;) s+=a[r++];//先将sum+a[r],再将r++
if (s<sum) break;
ans=min(r-l,ans);//求出此时最小区间
s-=a[l++];//左端右移
}
printf("%d\n",ans==0x3f3f3f3f?0:ans);//所有数字的和都小于s则输出0
}
}
UVA 11572 Unique Snowflakes
给出一串数字,求使得区间内所有数字都不相同的最长区间长度。
依然可以暴枚,对吧?肯定会T。
由于数据的范围在10^9之内,不能O(n)出答案。我们可以用map或者set存储每个数字有没有出现过,然后依然用尺取法从左到右扫。这个是我以前写的代码,有一些看起来不舒服了。
#include<bits/stdc++.h>
using namespace std;
const int boss=1e6;
map<int,int> snow;
int a[boss+10],n,answer=1;
void searchs()
{
answer=1;snow.clear();
int l=1,r=2;snow[a[l]]++,snow[a[r]]++;
while (r<=n)
{
snow[a[++r]]++;
while (snow[a[r]]>1)
{
snow[a[l++]]--;
if (l>=r) continue;
}
answer=r>n?max(answer,r-l):max(answer,r-l+1); //存储的是l为左端点,r为右端点。如果r>n就变成右端点+1了。
}
}
int main()
{
int i,t;
scanf("%d",&t);
while (t--)
{
scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d",&a[i]);
searchs();
printf("%d\n",answer);
}
}
洛谷 1638 逛画展
给出一串数字,求使得使1-m每个数字都出现过至少一次的最短区间长度。
依然是一样的做法,由于m<=2000,可以用数组存储,因此复杂度是O(n).
#include<bits/stdc++.h>
#define boss 1000000
using namespace std;
int n,m,a[boss+10],cnt[2010];
inline int read()
{
int x=0;char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}
int main()
{
n=read(),m=read();
int i;
for (i=1;i<=n;++i) a[i]=read();
int s=1,t=1,num=0,res=n,l=1,r=n;//num表示从l-r一共出现了几种数字
for (;;)//==while(true)
{
for (;t<n&&num<m;) if (cnt[a[t++]]++==0) num++;
if (num<m) break;
if (res>t-s) res=t-s,l=s,r=t-1;
if (--cnt[a[s++]]==0) num--;
}
printf("%d %d",l,r);
}
所谓尺取法,就是用两个指针从左到右扫来解决优化区间问题。不是什么题都能随便用尺取法来做的,弄不好会因为枚举不全面而wa掉。