尺取法

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

例题

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值