尺取法,是一种 简单而有效的小算法,
在外国网站上,它的名字叫做 two pointers
,直译过来就是 双指针,
这个外国名直接概括了尺取法的特点:
定义两个变量 \(l\) 和 \(r\)(不一定 是指针),
一开始先对 \(l\) 进行 移动操作,
直到 \(l\) 确定 为止。
之后,我们开始移动 \(r\),
等到 \(r\) 确定时,
就可以 记录答案。
例题:
1. POJ3061 Subsequence
温馨提示:题面是英语
给定长度为 \(N\) 的数组 \(A\) 和一个整数 \(M\),求总和不小于 \(M\) 的 子串 的 最小长度。
举个栗子:
\[n=10,m=15\]
\[A=\{5,1,3,5,10,7,4,9,2,8\}\]
那么我们先定义一个变量 \(sum\),存 当前子串的和,从左边第一个数开始,直到 \(sum\ge M\) 为止,再记录 当前长度。
其实,
这个思想相当于 不满足条件就入队,
然后得到队列长度,
再将队首元素 出队,
再进行下一次的入队,
直到满足条件再次出队,
并且将这一次的长度与历史最短长度进行 取舍。
遍历到最后的元素 无法再满足入队条件 的时候就结束。
此时用 \(O(n)\) 级别的时间复杂度就可以得到答案。
Code:
#include<bits/stdc++.h>
#define min(a,b) ((a)<(b)?(a):(b))
#define tense(a,b) (a=min(a,b))
using namespace std;
int t,n,m,a[100005];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
int ans=n+1,now=0,flag=0; //答案设为最大值(n+1)
for(int i=1;i<=n;i++)
scanf("%d",a+i);
queue<int>q;
for(int i=1;i<=n;i++)
{
q.push(a[i]);
now+=a[i];
if(now>m)
{
while(now>=m) //记录答案
{
flag=1;
int tmp=q.size();
tense(ans,tmp);
now-=q.front();
q.pop();
}
}
}
if(!flag)
puts("0");
else
printf("%d\n",ans);
}
return 0;
}