【记录】四道双指针

教练给了四道双指针,品鉴中。


双指针通过两个指针的协同工作,避免不必要的重复遍历,从而提高算法效率。

时间复杂度一般在 左右,分为同向移动和双向移动两种。

大多都是在维护一个区间,要求这个区间满足某种性质,且包含的元素最少(或求可能的个数)。


1.USACO16OPEN Diamond Collector S

P3143 USACO16OPEN Diamond Collector S - 洛谷 (luogu.com.cn)

排序是显然的,在排好序的数列中找出两段长度和最大的不重合的区间,并使两个区间中的最大值与最小值的差不大于 k。

可以枚举一个分割点 分割两端区间,定义 l 和 r 作为 两端区间的起始和结尾。

定义 mx 为 点左边(不包含 )最多可以取多少点,每次更新答案就是 mx + r - i + 1。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 5e4 + 10;

LL a[N];

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int n; LL K;
	cin >> n >> K;
	for (int i = 1; i <= n; i ++) {
		cin >> a[i];
	}
	sort (a + 1, a + n + 1);
	
	LL mx = 1, ans = 1; int l = 1, r = 2;
	for (int i = 2; i <= n; i ++) {
		while (a[i - 1] - a[l] > K && l < n) {
			l ++;
		}
		while (a[r] - a[i] <= K && r < n) {
			r ++;
		}
		if (a[r] - a[i] > K) {
			r --;
		}
		mx = max(mx, 1ll * i - 1 - l + 1);
		ans = max(ans, mx + r - i + 1);
	}
	cout << ans << "\n";
	
	return 0;
}

2.单词背诵

P1381 单词背诵 - 洛谷 (luogu.com.cn)

要维护一个包含最多要背单词的最短区间。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int a[N];
map<string, int> mpa;
map<string, int> mpb;
map<int, int> mp;

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int n;
	cin >> n;
	int cnta = 0;
	for (int i = 1; i <= n; i ++) {
		string s;
		cin >> s;
		if (!mpa[s]) {
			cnta ++;
			mpa[s] = cnta;
		}
	}
	
	int m;
	cin >> m;
	memset(a, 0, sizeof(a));
	int cntb = 0;
	for (int i = 1; i <= m; i ++) {
		string s;
		cin >> s;
		if (mpa[s]) {
			if (!mpb[s]) {
				cntb ++;
				mpb[s] = cntb;
			}
			a[i] = mpb[s];
		}
	}
	cout << cntb << "\n";
	if (cntb == 0) {
		cout << "0\n";
		return 0;
	}
	
	int sum = 0, ans = 1e9;
	for (int i = 1, j = 0; i <= m; i ++) {
		while (sum < cntb && j < m) {
			j ++;
			if (a[j] != 0) {
				if (!mp[a[j]]) {
					sum ++;
				}
				mp[a[j]] ++;
			}
		}
		if (sum == cntb) {
			ans = min(ans, j - i + 1);
		}
		if (mp[a[i]]) {
			mp[a[i]] --;
			if (!mp[a[i]]) {
				sum --;
			}
		}
	}
	cout << ans << "\n";
	
	return 0;
}

3.逛画展

P1638 逛画展 - 洛谷 (luogu.com.cn)

要维护一个包含所有大师画的最短区间。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int a[N];
map<int, int> mp;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) {
		cin >> a[i];
	}
	
	int cnt = 0, st = 1e9, ed = 1e9, len = 1e9;
	for (int i = 1, j = 0; i <= n; i ++) {
		while (cnt < m && j < n) {
			j ++;
			if (!mp[a[j]]) {
				cnt ++;
			}
			mp[a[j]] ++;
		}
		if (cnt == m && j - i + 1 < len) {
			st = i;
			ed = j;
			len = j - i + 1;
		}
		mp[a[i]] --;
		if (!mp[a[i]]) {
			cnt --;
		}
	}
	cout << st << " " << ed << "\n";
	
	return 0; 
}

4.ABC098D Xor Sum 2

AT_arc098_b ABC098D Xor Sum 2 - 洛谷 (luogu.com.cn)

注意到窗口内所有数的二进制表示中,每一位最多只有一个数在该位上是 1。

因为当两个数在同一个二进制位上都是 1 时,加法会产生进位,而异或不会。

所以满足题目要求的一段中的任何一段都是满足要求的。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
LL a[N];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int n;
	cin >> n;
	for (int i = 1; i <= n; i ++ ) {
		cin >> a[i];
	}
	
	LL ans = 0;
	LL sum = 0, res = 0;
	for (int i = 1, j = 0; i <= n; i ++) {
		while (sum == res && j < n) {
			j ++;
			sum += a[j];
			res ^= a[j];
		}
		if (sum != res) {
			sum -= a[j];
			res ^= a[j];
			j --;
		}
		ans += (j - i + 1);
		sum -= a[i];
		res ^= a[i];
	}
	cout << ans << "\n";
	
	return 0; 
}
相关推荐
卷无止境2 小时前
Eigen 库如何借助 OpenMP 加速计算
c++·后端
_清歌3 小时前
DSpark 深度解读:DeepSeek-V4 如何用「半自回归」把推理速度提升 85%
算法
统计实现局3 小时前
SVD 的三步走:双对角化、Givens 收敛、排序
算法
躬行见万象3 小时前
《VLA 系列》UniLab 强化训练 | G1 机器人 |复现
算法
统计实现局3 小时前
对称不定分解(Bunch-Kaufman):为什么 Cholesky 不够用
算法
统计实现局3 小时前
dqrsl 拆解:拿着 QR 结果能算出哪 5 种东西
算法
卷无止境3 小时前
OpenMPI、MPICH 与 OpenMP:关系、核心概念与架构全解
c++·后端
统计实现局3 小时前
为什么 Cholesky 求逆比 Gauss-Jordan 快一倍——行列式溢出防护详
算法
To_OC15 小时前
LC 994 腐烂的橘子:人人都说是 BFS 入门题,我却写了三遍才过
javascript·算法·leetcode