杂项之尺取法与双指针

基本知识

  • 定义: 反复推进区间的开头和末尾,来求满足条件的最小区间,最大区间的方法被称为尺取法。
  • 用法: 故名思义,尺取法的有两个端点,一个代表头 i ,一个代表尾 j 。与二分法很相像,二分法是求满足条件的最小值最大值,而尺取法是求满足条件的最小区间最大区间。其题目通常要满足:连续(在一个区间上),单调(区间越大越容易满足条件或者越不容易满足条件)
  • 思路: 与二分法做对比
  • 二分法: 枚举左端点,二分找到满足条件的最小或最大右端点,更新答案,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
  • 尺取法: 枚举左端点,单调向右移动右端点直到刚好满足要求或刚好不满足要求,由于左右端点均递增,时间复杂度 O ( n ) O(n) O(n)

模板

模板1(单调的找到符合条件为止)

int main(){
	int j=0;//右端点
	for(int i=1;i<=n;i++){
		while(没满足条件&&j<n)区间右端点右移,并计算影响 
		if(没满足条件)break;
		计算答案
	    去掉左端点右移的影响 
	}
}

例题汇总

枚举左端点,尺取右端点

例题1:和大于等于 S 的最小区间

  • 题目描述: 有一个 n 个数的数字 ai,求长度最小的一个区间,使得该区间的和 > = S >=S >=S。其中 a i > = 0 ai>=0 ai>=0
  • 问题分析: 尺取模板题
int j=0,ans=1e9,sum=0;
for(int i=1;i<=n;i++) {
	while(j<n&&sum<s)sum+=a[++j];
	if(sum<s)break; 
	ans=min(ans,j-i+1);
	sum-=a[i]; 
}

例题2:能覆盖所有值的最小区间

  • 题目描述: 一共有 m 个知识点。一个书有 n 页纸,每页纸都有一个知识点 ai。求最少需要阅读连续的多少页纸,使得所有知识点均覆盖。
  • 问题分析: 来个桶对知识点计数,桶从0到1表示知识点数量+1,桶从1到0表示知识点数量-1。
int j=0,ans=1e9,num=0;
for(int i=1;i<=n;i++){
	while(j<n&&num<m){
		count[a[++j]]++;
		if(count[a[j]]==1)num++;
	} 
	if(num<m)break; 
	ans=min(ans,j-i+1);
	count[a[i]]--;
	if(count[a[i]]==0)num--;
}

例题3:通过价值排序,使得尺取过程具有单调性

例题链接

  • 题目描述: x 轴上有 1 − m 1-m 1m ,有 n 个线段,每条线段从 l i l_i li r i r_i ri ,价值为 w i w_i wi 。你可以选择若干条线段,当你选择了一条线段之后,你可以在这条线段上随意的移动,而此时选择的花费为:选择的若干条线段的最大价值与最小价值之差。问最少要多少的花费,使得可以从 1 走到 m 。
  • 问题分析: 最大值与最小值差的最小值 。我们对线段按价值排个序,即可转化为最小区间问题。很显然,选择的线段数量越多,条件越任意满足,而价值又单调,那么可以直接尺取了。如何判断条件是否满足呢?线段树维护区间加,然后区间最小值即可判断出是否覆盖了 1 − m 1-m 1m 的所有点。
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].w);
	sort(a+1,a+n+1,cmp);
	
	int j=0;
	for(int i=1;i<=n;i++){
	    while(j<n&&tr[1]==0){
	        end++;
	        update(1,1,m,a[j].l+1,a[j].r,1);
	    }
	    if(tr[1]==0)break;
	    ans=min(ans,a[j].w-a[i].w);
	    update(1,1,m,a[i].l+1,a[i].r,-1);
	}
	cout<<ans;
}

例题4:通过对前缀和排序,使得尺取过程具有单调性

  • 题目描述: N个数字组成的数列,T个询问,每次询问给定一个 k,找出数列中一段和的绝对值与 k 相差最小的,输出和的绝对值以及起点和终点。
  • 思路: 一般尺取法只适用单调数列(一般和值递增),为了构造这样的性质:我们采用对前缀和从小到大排序的思路,这样枚举前缀和就可以枚举出区间,而区间越大,差值越大,符合单调性。
int main() {
	int n,T,k;
	cin>>n>>T>>k;
	for(int i=1; i<=n; i++)scanf("%d",&a[i]);
	for(int i=1; i<=n; i++) {
		p[i].v=p[i-1].v+a[i];
		p[i].id=i;
	}
	sort(p+1,p+n+1,cmp);

	int j=0,sum=0,ansl,ansr;
	for(int i=0; i<=n; i++) {
		while(sum<t&&j<n) {
			sum=p[++j].v-p[i].v;
			if(minn>abs(sum-k)) {
				minn=abs(sum-k);
				ans=sum;
				ansl=p[i].id;
				ansr=p[j].id;
			}
		}
		if(sum<t)break;
	}
	if(ansl>ansr)swap(ansl,ansr);
	cout<<ans<<" "<<ansl+1<<" "<<ansr<<'\n';
}

例题5:gcd>1 的最大区间

  • 题目描述: n 个数,求 g c d > 1 gcd>1 gcd>1 的区间的最大长度为多少。
  • 问题分析: 很显然符合单调性,即区间越小越容易满足条件,区间越大越不容易满足条件。那么就用模板3,st表维护区间gcd即可。时间复杂度O(nlogn)
int main() {
	int j=1,ans=0;
	for(int i=1; i<=n; i++) {
		while(j<=n&&query(i,j)>1)j++;
		if(j>n)break;
		ans=max(ans,j-i);
	}
}

例题6:比 mid 大的数超过 k 个的区间个数

  • 题目描述: n 个数 a i a_i ai ,求比 mid 大的数超过 k 个的区间个数
  • 问题分析: 显然区间越大,比 mid 大的数超过 k 个的条件更容易满足。对于左边界 i ,当边界尺取到 j 不满足题意时,左边界在 i , 右边界在 [ j , n ] [j,n] [j,n] 的所有区间都满足条件了。统计即可。
int main(){
	int j=0,sum=0.ans=0;
	for(int i=1;i<=n;i++){
		while(sum<k&&j<n){
			j++;
			if(a[j]>mid)sum++;
		}
		if(sum<k)break;
		ans+=n-j+1;
	}
}

例题7:排序形成尺取

  • 问题分析: 给定 n 个人的成绩 a i , b i a_i,b_i ai,bi,表示第 i i i 个人可能考到 a i a_i ai,也可能考到 b i b_i bi 。给定 p p p,定义及格:假设 n n n 个人中最高分为 x x x,则比 x × p % x\times p\% x×p% 高的人均及格。求 n 个人中最多有多少个人及格。
  • 问题分析: 将 n 个人的两个成绩,放在一起,从小到大排序。枚举右端点表示最高分,尺取左端点推进到第一个 > = x × p % >=x\times p\% >=x×p% 的位置。则之间的人均有可能及格。判断一下合法性,再更新一下答案即可。合法性判断:最高分一定要比所有人最低分的最大值大。
struct node{
	int v,id;
}p[N];
int cmp(node x,node y){
	return x.v<y.v;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>p[++cnt].v;
		p[cnt].id=2*i-2;
		cin>>p[++cnt].v;
		p[cnt].id=2*i-1;		
		int t=min(p[cnt-1].v,p[cnt].v);
		down=max(down,t);
	}
	sort(p+1,p+cnt+1,cmp);
	
	int i=1;//0,1  2n-2,2n-1
	for(int j=1;j<=cnt;j++){
		if(vis[p[j].id^1]==0)sum++;
		vis[p[j].id]=1; 
		while(p[i].v<p[j].v*1.0*p/100&&i<j){
			if(vis[p[i].id^1]==0)sum--;
			vis[p[i].id]=0;
			i++;
		} 
		if(p[j].v<down)continue;
		else ans=max(ans,sum); 
	}
	cout<<ans;
}

双指针从左右端点一起往内移动

例题1:在升序排列的整数数组中,找到两个数和为 x x x

  • 题目描述: 给定 n 个数,其已经按照升序排序完成。给定 x ,请你在数组中找到两个数满足相加之和等于 x 。
  • 问题分析: 双指针 i , j i,j i,j 最开始分别指向 1 , n 1,n 1,n
  • a i + a j > x a_i+a_j>x ai+aj>x,则 j − − j-- j
  • a i + a j < x a_i+a_j<x ai+aj<x,则 i + + i++ i++
  • a i = a j a_i=a_j ai=aj,则已找到
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值