尺取法学习笔记
作者:互联网
什么是尺取法
Codeforces 中显示它的算法名称叫做 "two pointers"
直译成中文的话叫双指针法
这个算法 hin 有意思,由于在某些巨佬眼中过于简单,以至于都没把尺取法当成一个算法
如何进行尺取法
尺取法的思想是维护两个指针 \(l,r\) ,分别为左端点与右端点,每当确定左端点时,尝试将右端点一直移动,直到不满足条件为止
,让我们来看这一道例题
求刚好有 \(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;
}
求有多少个区间 \([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