教练给了四道双指针,品鉴中。
双指针通过两个指针的协同工作,避免不必要的重复遍历,从而提高算法效率。
时间复杂度一般在 左右,分为同向移动和双向移动两种。
大多都是在维护一个区间,要求这个区间满足某种性质,且包含的元素最少(或求可能的个数)。
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.逛画展
要维护一个包含所有大师画的最短区间。
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;
}