ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

尺取法学习笔记

2021-08-07 12:34:59  阅读:135  来源: 互联网

标签:const int 笔记 学习 取法 端点 区间 include


什么是尺取法

Codeforces 中显示它的算法名称叫做 "two pointers"

直译成中文的话叫双指针法

这个算法 hin 有意思,由于在某些巨佬眼中过于简单,以至于都没把尺取法当成一个算法

如何进行尺取法

尺取法的思想是维护两个指针 \(l,r\)​ ,分别为左端点与右端点,每当确定左端点时,尝试将右端点一直移动,直到不满足条件为止

,让我们来看这一道例题

P1638 逛画展

求刚好有 \(m\) 种数字的最短区间

我们发现,当这个区间的左端点向右移动时,右端点一定不会向左移动,所以我们在枚举 \(r\) 时,不需要从 \(l\) 开始枚举,我们可以从上一次枚举到的 \(r\) 开始枚举

我们记录一个 \(cnt\) 数组, \(cnt_i\) 表示第 \(i\) 种数字在 \(l \sim r-1\) 这个区间内的出现次数

用一个变量 \(sum\) 来表示这个区间不同画的数量

维护一下,即可AC

具体细节见代码:

#include <cstdio>
using namespace std;
const int N=1e6+7,MAX=2e3+7;

int a[N];
int cnt[MAX];

int n,m;
int ansl,ansr=N; // 初始化区间长度为最长

signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d",a+i);
	for(int l=1,r=1,sum=0;l<=n;) {
		while(r<=n && sum<m) { // 判断是否满足条件
			++cnt[a[r]]; // 数量+1
			if(cnt[a[r]]==1) // 如果数量+1后只有一个,说明出现了新的数,++sum
				++sum;
			++r; // 移动右端点指针
		}
		
		// 这里的右端点指针指向的是最长能向右扩展的位置的下一位
		// 所以区间长度是(r-1)-l+1=r-l
		// 而不是r-l+1
		if(sum==m && r-l<ansr-ansl)
			ansl=l,ansr=r; // 如果有更短的满足题设的区间,更新答案
		
		--cnt[a[l]]; // 移动左端点指针
		if(!cnt[a[l]]) // 如果移动后这个数的数量为0,说明减少了一个数,--sum
			--sum;
		++l;
	}
	printf("%d %d",ansl,ansr-1); // 根据右端点的定义,输出时右端点要-1
    return 0;
}

习题:

UVA11572 唯一的雪花 Unique Snowflakes

求没有重复数字的最长区间

与第一题思路类似,由于值域范围较大,我使用的是 set 维护

#include <set>
#include <cstdio>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=1e6+7;

set<int> s;

int a[N];

int T,n,ans;

signed main() {
	scanf("%d",&T);
	for(;T;--T) {
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",a+i);
		s.clear(); // 清空
		ans=-inf;
		for(int l=1,r=1;l<=n;) {
			while(s.find(a[r])==s.end() && r<=n)
				s.insert(a[r]),++r; // 移动右端点指针
			ans=max(ans,r-l); // 计算答案
			s.erase(a[l]);
			++l; // 移动左端点
		}
		printf("%d\n",ans);
	}
    return 0;
}

AT4142 [ARC098B] Xor Sum 2

求有多少个区间 \([l,r]\) ,满足 \(a_l \ xor \ a_{l+1} \ xor \dots xor \ a_r = a_l + a_{l+1} + ... +a_r\)

注意到一个性质,当 \(a \ and \ b=0\) 时,\(a \ xor \ b=a+b\)​

那么 \(l \sim r\) 所有数的异或值等于所有数的和,必须要每一个二进制位上该区间所有数加起来最多只有一个 \(1\) 才行.

直接用尺取法做,记得开 long long

#include <cstdio>
typedef long long ll;
using namespace std;
const int N=2e5+7;

int a[N];

ll ans;
int n;

signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",a+i);
	for(int l=1,r=1,tmp=0;r<=n;) {
		while(!(tmp&a[r]) && r<=n) { // tmp&a[r]!=0,则tmp^a[r]!=tmp+a[r]
			tmp+=a[r],++r; // 移动右端点指针
			ans+=r-l; // 统计答案
		}
		tmp^=a[l],++l; // 移动左端点指针
	}
	printf("%lld",ans);
    return 0;
}

P3143 [USACO16OPEN]Diamond Collector S

求最长的两端不相交的区间,每个区间的极差不大于 \(k\)

本题我们可以用尺取法做

要求区间互不相交,我们统计答案就要变成左端点前的最大区间+当前区间

#include <cstdio>
#include <algorithm>
using namespace std;
const int N=5e4+7;

int a[N];
int c[N];

int k;
int n,ans;

signed main() {
	scanf("%d%d",&n,&k);
	for(ll i=1;i<=n;++i)
		scanf("%d",a+i);
	sort(a+1,a+1+n); // 因为放置与顺序无关,所以我们可以先排序,使得相邻两数之差变小
	for(int l=1,r=2,maxx=-1;l<=n;) {
		while(a[r]-a[l]<=k && r<=n)
			++r; // 移动右端点指针
		c[r]=max(c[r],r-l); // 记录以r为右端点向左可以扩展的最大区间
		maxx=max(maxx,c[l]); // 更新之前的最长区间
		ans=max(ans,maxx+(r-l)); // 更新答案为之前的最长区间+当前区间
		++l;
	}
	printf("%d",ans);
    return 0;
}

标签:const,int,笔记,学习,取法,端点,区间,include
来源: https://www.cnblogs.com/wshcl/p/two-pointers.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有