【ACM常用技巧】尺取法

反复推进区间的开头和末尾,来求满足条件的最小区间的方法被称为尺取法。


尺取法的四个要素:

  • 什么时候使用尺取法?
  • 什么时候推进端点?
  • 怎么样推进端点?
  • 什么时候结束枚举区间?

下面通过几个例题来说明一下尺取法应该如何使用:

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;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值