尺取法

来源:互联网 发布:网络平台都有哪些功能 编辑:程序博客网 时间:2024/04/29 06:56

在符合一定条件的区间问题中,我们可以利用尺取法降低一维的复杂度。具体直接看例题有解释。当年我自己推出了尺取法,现在知道有这种方法却不会做题了。

例题

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 1000000using 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掉。

原创粉丝点击