双指针扩展-三指针笔记

双指针扩展-三指针笔记

在使用双指针维护一段序列时,可能会出现多个区间的情况,这时若是仅采用双指针移动,则会漏掉部分情况

可以再加入一个指针来,使两个指针维护一个子序列,使子序列和另外一个指针共同维护

这里以洛谷P1102为例:

我们需要对一段序列求满足条件的数对的数量,但由于序列中元素可以重复,所以两个快慢指针会漏掉一部分情况:

2 2 3 3 4 5 5 5 6 7 

假设 \(i\) 指针和 \(j\) 指针从头开始,当我们计算的 \(A-B=C=3\) 时,数对数加一。

for (int i = 1, j=1; i <= n; i++) {
	while (i >= j && a[i] - a[j] >= c) {//保证i在j前面
		j++;
		if (a[i] - a[j] == c) ans++;
	}
}

\(i\) 运动到第一个 \(5\) 时,满足要求,此时移动 \(j\) 到第二个 \(2\),再次满足要求,在移动 \(j\) 指向 \(3\)不满足要求

\(i\) 移动,但是会发现 \(i\) 仍然指向的是 \(5\)\(j\)移动出了 \(2\) 所在的区间,则答案会丢失一部分

采用三指针维护:

for (int i = 1, j1 = 1, j2 = 1; i <= n; i++) {
	while (i >= j1 && a[i] - a[j1] >= c)
		j1++;
	while (i >= j2 && a[i] - a[j2] > c)
		j2++;
	ans += j1 - j2;
}

我们让 \(j_1\) 这个指针指向满足 \(A-B=C=3\) 时,\(j\) 序列的最后元素的下一个元素,即指向 \(2\) 之后的 \(3\)\(j_2\) 指向 \(j\) 序列的第一个元素,即指向第一个 \(2\)

ps:

a[i] - a[j1] >= c 可移项为 a[j1] <= a[i] - c
a[i] - a[j2] > c 可移项为 a[j2] < a[i] - c
可以看出在有序数列中 j2是小于j1 的

这时 \(j_1-j_2\) 就是 \(j\) 序列的长度,随后移动 \(i\) 指针,直到移动出 \(5\) 的区间,\(ans\) 每次都加上 \(j\) 序列的长度,保证不漏

完整代码:

#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;

int a[200010];
int n, c;
long long ans;
int main()
{
	scanf("%d %d", &n, &c);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	sort(a + 1, a + n + 1);
	for (int i = 1, j1 = 1, j2 = 1; i <= n; i++) {
		while (i >= j1 && a[i] - a[j1] >= c)
			j1++;
		while (i >= j2 && a[i] - a[j2] > c)
			j2++;
		ans += j1 - j2;
	}
	printf("%lld", ans);
	return 0;
}
posted @ 2024-11-22 11:25  才瓯  阅读(18)  评论(0)    收藏  举报