反复推进区间的开头和末尾,来求满足条件的最小区间的方法被称为尺取法。
尺取法的四个要素:
- 什么时候使用尺取法?
- 什么时候推进端点?
- 怎么样推进端点?
- 什么时候结束枚举区间?
下面通过几个例题来说明一下尺取法应该如何使用:
POJ 3061 Subsequence 【传送门】
给定长度为n的一个正整数序列,以及整数S。求出总和不小于S的连续子序列的最小长度。如果解不存在,则输出0
分析:由于序列都是整数,我们设置两个端点一个代表头(st),一个代表末尾(en)这是尺取法的两个关键端点,初始值都为0(即数组的开始),然后我们用一个死循环来遍历这个数组,当末尾端点en小于n并且sum小于S的时候,累加sum并累加en,直到en大于n或者sum大于S为止。然后判断sum与S的关系,如果这段区间和小于S则break跳出死循环。否则取ans与en-st的最小长度,然后sum减去开始的端点的值,继续执行下一步直到最后……
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<algorithm>
#include<cmath>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define INF 1<<30
#define sscc ios::sync_with_stdio(false)
const int MAXN = 100000+5;
typedef unsigned long long ull;
int n,S;
int a[MAXN];
void solve(){
int sum=0;
int ans=INF;
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;
}
int main(){
sscc;
int T;
cin >> T;
while(T--){
mem(a,0);
cin >> n >> S;
for(int i=0;i<n;i++)
cin >> a[i];
solve();
}
return 0;
}
POJ 3320 Jessica's Reading Problem 【传送门】
课本总共有P页,第i页恰好有一个知识点ai,全书中同一个知识点可能会被多次提到,所以她希望通过阅读其中一些页把所有的知识点都覆盖到。
分析:和上面的题目一样,设置两个端点,用set来记录总共有多少个知识点,用map存储知识点到出现次数的映射,然后仿照上面的板子就可以了。
注意:用cin,cout会超时……
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<algorithm>
#include<cmath>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define INF 1<<30
#define sscc ios::sync_with_stdio(false)
const int MAXN = 100000+5;
typedef unsigned long long ull;
int main(){
// sscc;
set<int> s;
int P;
int a[MAXN];
scanf("%d",&P);
for(int i=0;i<P;i++){
scanf("%d",&a[i]);
s.insert(a[i]);
}
int n=s.size();
int st=0,en=0,ans=INF,num=0;
map<int,int> count;
while(1){
while(en<P&&num<n)
if(count[a[en++]]++ ==0)
num++;
if(num<n) break;
ans=min(ans,en-st);
if(--count[a[st++]]==0)
num--;
}
printf("%d\n",ans);
return 0;
}