尺取法,顾名思义,就是像尺子一样,一块一块的截取。尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以尺取法是一种高效的枚举区间的方法,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案。
使用尺取法时应清楚以下四点:
1、 什么情况下能使用尺取法? 2、何时推进区间的端点? 3、如何推进区间的端点? 3、何时结束区间的枚举?
尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,通俗地说,在对所选取区间进行判断之后,我们可以明确如何进一步有方向地推进区间端点以求解满足条件的区间,如果已经判断了目前所选取的区间,但却无法确定所要求解的区间如何进一步得到根据其端点得到,那么尺取法便是不可行的。首先,明确题目所需要求解的量之后,区间左右端点一般从最整个数组的起点开始,之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。
1.poj 3061
给定长度为n的数列整数a0,a1,a2,a3 ..... an-1以及整数S。求出综合不小于S的连续子序列的长度的最小值。如果解不存在,则输出0。
限制条件:
10<n<10^5
0<ai<10^4
S<10^8
这里我们拿第一组测试数据举例子,即 n=10, S = 15, a = {5,1,3,5,10,7,4,9,2,8}
整个过程分为四步
1.初始化左右端点
2.不断扩大右端点,直到满足条件
3.如果第二步无法满足条件,则终止,否则更新结果
4.将左端点扩大1,回到第二步
附代码
#include <iostream>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
ll a[100010];
int n,t,ans = INF;
ll sum,s;
int main()
{
int t;
cin >> t;
while(t--)
{
cin >> n >> s;
sum = 0,ans = INF;
for(int i = 0;i<n;i++)
cin >> a[i];
int st = 0,en = 0;
while(1)
{
while(en < n && sum < s)
sum += a[en++];
if(sum < s)
break;
ans = min(ans,en-st);
sum -= a[st++];
}
if(ans == INF)
ans = 0;
cout<<ans<<endl;
}
return 0;
}
2. poj 3320
题意:
书一共p页,第i页有一个知识点a[i],同一个知识点可以多次出现,想要读连续的书页把所有的知识点覆盖,求要读的最少的页数
思路:
首先set记录一共有多少知识点
假设在区间[st,en]已经覆盖了所有的知识点,我们可以从st开始,把st取走后,那么页st上的知识点出现次数就要减一,如果此时这个知识点的出现次数为0了,那么,在同一个知识点出现之前,不停地将区间末尾en向后推进即可。
附代码
#include <iostream>
#include <set>
#include <map>
using namespace std;
const int Max = 1e6+10;
int a[Max];
int main()
{
int n;
while(cin >> n)
{
set<int> s;
map<int,int> mp;
for(int i = 0;i<n;i++)
{
cin >> a[i];
s.insert(a[i]);
}
int st = 0,en = 0;
int num = 0,ans = n+1;
while(1)
{
while(en < n && num < s.size())
{
if(mp[a[en]] == 0)
num++;
mp[a[en]]++;
en++;
}
if(num < s.size())
break;
ans = min(ans,en-st);
if(mp[a[st]] == 1)
num--;
mp[a[st]]--;
st++;
}
cout<<ans<<endl;
}
}