双指针
1. 算法分析
通过使得两个指针都不断向右移,将 O ( n 2 ) O(n^2) O(n2)的算法优化到 O ( n ) O(n) O(n)
2. 模板
2.1 维护窗口双指针
for (int i = 0, j = 0; i < n; ++i) {
while (j < i && check(i, j)) j++;
}
2.2 区间分割双指针
for (int i = 0, j = 0; i < s.size(); i = j) {
j = i;
while(j < s.size() && s[i] == s[j]) j++; // 维护的区间为[i, j)
v[cnt] = s[i] - '0'; // 维护的区间的开头为s[i] - '0'
sz[cnt] = j - i; // 维护的区间的长度为:j - i
cnt++; // 区间数目+1
}
3. 典型例题
3.1 维护窗口双指针
acwing799.最长连续不重复子序列
题意: 给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度。1≤n≤100000
题解: 双指针维护一个区间,区间内每个数字都只出现过一次
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 1e6 + 10;
int a[N];
int main() {
int n;
cin >> n;
int res = 0;
unordered_map<int, int> st;
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
for (int i = 0, j = 0; i < n; ++i) {// 用双指针维护一个区间,区间内的数字只出现一次
st[a[i]]++;
while (j < i && st[a[i]] > 1) { // 当发现区间内的数字出现两次,移动j指针
st[a[j]]--;
j++;
}
res = max (res, i - j + 1); // 更新区间内的最多数据数目
}
cout << res << endl;
return 0;
}
acwing1238. 日志统计
题意: 小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。其中每一行的格式是:
ts id
表示在 ts 时刻编号 id 的帖子收到一个”赞”。现在小明想统计有哪些帖子曾经是”热帖”。如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
1
≤
K
≤
N
≤
1
0
5
,
0
≤
t
s
,
i
d
≤
1
0
5
,
1
≤
D
≤
10000
1≤K≤N≤10^5,0≤ts,id≤10^5,1≤D≤10000
1≤K≤N≤105,0≤ts,id≤105,1≤D≤10000
题解: 首先按照时间从小到大排序,然后双指针维护一个区间,cnt记录当前区间里面每个id的出现次数,如果次数大于等于k,那么该id为热帖。
代码:
#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
int const N = 1e5 + 10;
typedef pair<int, int> PII;
PII logs[N];
int n, d, k, cnt[N], st[N];
int main() {
cin >> n >> d >> k;
for (int i = 0; i < n; ++i) {
cin >> logs[i].x >> logs[i].y;
}
sort(logs, logs + n);
for (int i = 0, j = 0; i < n; ++i) {
int id = logs[i].y;
int ts = logs[i].x;
cnt[id]++; // 当前id次数加一
while(j < i && ts - logs[j].x >= d) { // 确保当前区间长度为d
cnt[logs[j].y]--;
j++;
}
if (cnt[id] >= k) st[id] = 1; // 如果符合条件
}
for (int i = 0; i < N; ++i) if (st[i]) cout << i << endl;
return 0;
}
3.3 区间分割双指针
Codeforces Round #681 B. Saving the City
题意: 给一段序列,1为有炸弹,0为无炸弹,要引爆所有炸弹,同时一个炸弹可以触发前后两个的炸弹。引爆一个炸弹的钱是a,放一个炸弹的钱是b。问引爆所有炸弹的最少花费。字符串长度为 1 0 5 10^5 105, 1 < = a , b < = 1000 1<=a,b<=1000 1<=a,b<=1000
题解: 如果想要把所有的炸弹引爆,那么要不然不按照炸弹。如果要安装炸掉,必然是先装0少的地方。每次填满一段0,就会使得1的连续数目段减一。所有只需要把0的段长度截出来,然后按照从小到大排序,不断往里面填炸掉。因此答案的为: m i n { a ∗ c n t 1 , a ∗ ( c n t 1 − 1 ) + c n t 0 [ 1 ] ∗ b , a ∗ ( c n t 1 − 2 ) + ( c n t 0 [ 1 ] + c n t 0 [ 2 ] ) ∗ b , . . . } min\{a* cnt_1, a*(cnt_1 - 1) + cnt_0[1] * b, a*(cnt_1-2)+(cnt_0[1]+cnt_0[2])*b,...\} min{a∗cnt1,a∗(cnt1−1)+cnt0[1]∗b,a∗(cnt1−2)+(cnt0[1]+cnt0[2])∗b,...}。需要不断抠出来1的段和0的段。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
int v[N], sz[N];
int main() {
cin >> T;
while(T--) {
memset(v, 0, sizeof v);
memset(sz, 0, sizeof sz);
cin >> n >> m;
string s;
cin >> s;
int cnt = 0;
for (int i = 0, j = 0; i < s.size(); i = j) {
j = i;
while(j < s.size() && s[i] == s[j]) j++;
v[cnt] = s[i] - '0';
sz[cnt] = j - i;
cnt++;
}
vector<int> vec;
int cnt_one = 0;
for (int i = 0; i < cnt; ++i)
if (v[i] == 0) {
if (i && i < cnt - 1) vec.push_back(sz[i]);
}
else cnt_one++;
sort(vec.begin(), vec.end());
LL res = (LL)n * cnt_one;
LL sum = 0;
for (int i = 0; i < vec.size(); ++i) {
sum += vec[i];
res = min(res, sum * m + (--cnt_one) * n);
}
cout << res << endl;
}
return 0;
}
Codeforces Global Round 11 B. Chess Cheater
题意: 给定一个长度为 2 ∗ 1 0 5 2*10^5 2∗105的字符串,字符串仅由’L’和’W’组成。如果当前位置是W,可以加一分,如果当前位置的前一个位置还是W,那么可以再加一分。现在给定m,表明最多可以把m个L变成W。问最多得分是多少。
题解: 总的得分可以等于 2 * w的数目 + 2 * k - L段没有填满的个数 - 1。因此,只需要把L段全部拿出来,尽可能把L段改成W,所以徐娅排序,然后从小到大取L段填。同时,首尾如果存在L,那么不需要取出来,因为如果首部存在,那么最后如果可以改的话,必然往后并,如果尾部存在,那么必然往前并,因此这两种情况,只需要最后多减一即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
int v[N], sz[N];
int main() {
cin >> T;
while(T--) {
cin >> n >> m;
memset(v, 0, sizeof v);
memset(sz, 0, sizeof sz);
string s;
cin >> s;
int cnt = 0;
LL res = 0;
for (int i = 0, j; i < s.size(); i = j) {
j = i;
while(j < s.size() && s[i] == s[j]) j++;
if (s[i] == 'W') v[cnt] = 0;
else v[cnt] = 1;
v[cnt] = (s[i] == 'W'? 0: 1);// w--0, l--1
sz[cnt] = j - i;
if (!v[cnt]) res += 2 * sz[cnt];
cnt++;
}
res = min(res + 2 * m, (LL)s.size() * 2);
vector<int> vec;
for (int i = 0; i < cnt; ++i) if (v[i] == 1) vec.push_back(sz[i]);
if (s.back() == 'L' && !vec.empty()) vec.pop_back(); // 删掉首部的L
if (s[0] == 'L' && !vec.empty()) vec.erase(vec.begin()); // 删掉尾部的L
sort(vec.begin(), vec.end());
reverse(vec.begin(), vec.end());
while(!vec.empty() && m - vec.back() >= 0) {
m -= vec.back();
vec.pop_back();
}
res = max((LL)0, res - (LL)vec.size() - 1);
cout << res << endl;
}
return 0;
}