CodeForces 2009G Yunli's Subarray Queries 题解
云璃!
高质量 Div.4,吊打某些 Div.2 Only / Edu / Div.3。
本题是下面四道题目的有机结合,优雅且经典。
Luogu P4168 [Violet] 蒲公英 | Luogu P1997 faebdc 的烦恼 | Luogu P3203 [HNOI2010] 弹飞绵羊 | Luogu P3246 [HNOI2016] 序列
题给函数 \(f(b)\) 表示通过任意修改 \(b\) 使之存在一个长不少于 \(k\) 的公差为 \(1\) 的等差数列的最少操作次数。令 \(a_i \leftarrow a_i - i\),则 \(f(b)\) 的含义变为通过任意修改 \(b\) 使之存在一个长不少于 \(k\) 的全部相等的子段的最少操作次数。
对于 easy version,显然全部修改为区间众数是最优的,回滚莫队求区间众数即可,时间复杂度 \(O(q \sqrt n)\)。(这也是 faebdc 的烦恼 的做法)
对于 hard version,对 \(1 \leqslant i \leqslant n - k + 1\),设 \(c_i\) 表示 \(f([i, i + k - 1])\)。\(c\) 可以用回滚莫队在 \(O(n \sqrt n)\) 的时间内求出。答案为区间 \([l, r - k + 1]\) 的所有前缀的 \(c\) 的最小值之和,即
考虑求这个值,对 \(1 \leqslant i \leqslant n - k + 1\),从 \(i\) 向 \(i\) 后面第一个比它小的位置 \(j\) 连边。(即满足 \(i < j \land c_i > c_j\) 的最小的 \(j\))前缀 \([i, j)\) 的最小值都为 \(i\),对答案的贡献为 \(i \cdot (j - i)\)。
每次暴力跳会超时,考虑分块处理出第一次跳出当前块会到达的位置和这时产生的总贡献,时间复杂度优化到 \(O(q \sqrt n)\),总时间为 \(O((n + q) \sqrt n)\),可以通过。(这也是 弹飞绵羊 的做法)
对于 extreme version,所求为 \(c\) 在区间 \([l, r - k + 1]\) 内的所有子区间的最小值之和,即
仍然考虑莫队,需要快速计算移动端点带来的贡献,即一段区间的所有前缀 / 后缀的最小值之和。
上一个 subtask 中求出了 \(c\) 中 \(i\) 之前第一个比它小的位置 \(pre_i\) 和它之后第一个比它小的位置 \(nxt_i\),如果继续套用 弹飞绵羊 的分块做法会超时。
容易发现前缀的贡献是可以差分的,所以设前缀贡献 \(f_i\) 和后缀贡献 \(g_i\),递推式为
但是直接差分错完了,因为前缀和中贡献是按整段计算的。设 \(p\) 为区间 \([l, r - k + 1]\) 之间最小值的位置,则其所有前缀 / 后缀的贡献分别为(令 \(r \leftarrow r - k + 1\))
于是莫队的移动端点可以在 \(O(1)\) 的时间内完成,总时间为 \(O((n + q) \sqrt n)\)。(这也是 序列 的做法)
然后因为常数过大被卡了。(垃圾 math.h)
稍微精细实现一下,用单调栈代替二分求 \(pre\) 和 \(nxt\),线性筛 \(\log\) 代替内置函数,就通过了。
好像官解不是这样做的,了解更多做法请见 官方题解。
接下来是很长的代码。
// easy version
#pragma GCC optimize("Ofast")
#include <algorithm>
#include <iostream>
using namespace std;
const int block_size = 450;
int n, k, q;
int a[200005];
int b[200005];
int rs[200005];
int val[200005], len;
struct node {
int l, r, id;
inline bool operator<(const node &nd) const {
return b[l] == b[nd.l] ? r < nd.r : b[l] < b[nd.l];
}
} qs[200005];
int cnt[200005];
int cnt2[200005];
int ans[200005];
static inline void solve() {
cin >> n >> k >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i] -= i;
val[++len] = a[i];
b[i] = (i - 1) / block_size + 1;
rs[b[i]] = i;
}
sort(val + 1, val + len + 1);
len = unique(val + 1, val + len + 1) - val - 1;
for (int i = 1; i <= n; ++i)
a[i] = lower_bound(val + 1, val + len + 1, a[i]) - val;
for (int i = 1; i <= len; ++i)
cnt[i] = 0;
for (int i = 1; i <= q; ++i) {
cin >> qs[i].l >> qs[i].r;
qs[i].id = i;
}
sort(qs + 1, qs + q + 1);
int curl = 1;
int curr = 0;
int las = 0;
int now = 0;
for (int t = 1; t <= q; ++t) {
auto [l, r, id] = qs[t];
if (b[l] == b[r]) {
int ret = 0;
for (int i = l; i <= r; ++i) {
++cnt2[a[i]];
if (cnt2[a[i]] > cnt2[ret])
ret = a[i];
}
ans[id] = r - l + 1 - cnt2[ret];
for (int i = l; i <= r; ++i)
--cnt2[a[i]];
continue;
}
if (b[l] != las) {
while (curr > rs[b[l]])
--cnt[a[curr--]];
while (curl < rs[b[l]] + 1)
--cnt[a[curl++]];
now = 0;
las = b[l];
}
while (curr < r) {
++cnt[a[++curr]];
if (cnt[a[curr]] > cnt[now])
now = a[curr];
}
int ret = now;
while (curl > l) {
++cnt[a[--curl]];
if (cnt[a[curl]] > cnt[ret])
ret = a[curl];
}
ans[id] = r - l + 1 - cnt[ret];
while (curl < rs[b[l]] + 1)
--cnt[a[curl++]];
}
for (int i = 1; i <= q; ++i)
cout << ans[i] << endl;
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("CF2009G.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--)
solve();
cout << flush;
return 0;
}
// hard version
#pragma GCC optimize("Ofast")
#include <algorithm>
#include <iostream>
#include <math.h>
using namespace std;
const int block_size = 450;
int n, k, q;
int a[200005];
int b[200005];
int ls[200005];
int rs[200005];
int val[200005], len;
struct node {
int l, r, id;
inline bool operator<(const node &nd) const {
return b[l] == b[nd.l] ? r < nd.r : b[l] < b[nd.l];
}
} qs[200005];
int cnt[200005];
int cnt2[200005];
int c[200005];
int st[200005][20];
static inline int query(int l, int r) {
int len = (int)log2(r - l + 1);
int x = st[l][len];
int y = st[r - (1 << len) + 1][len];
return c[x] < c[y] ? x : y;
}
int nxt[200005];
int rail[200005];
long long w[200005];
static inline void solve() {
// READ & BLOCK & HASH
cin >> n >> k >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i] -= i;
val[++len] = a[i];
b[i] = (i - 1) / block_size + 1;
rs[b[i]] = i;
}
for (int i = b[1]; i <= b[n]; ++i)
ls[i] = rs[i - 1] + 1;
sort(val + 1, val + len + 1);
len = (int)(unique(val + 1, val + len + 1) - val - 1);
for (int i = 1; i <= n; ++i)
a[i] = (int)(lower_bound(val + 1, val + len + 1, a[i]) - val);
for (int i = 1; i <= len; ++i)
cnt[i] = 0;
for (int i = 1; i <= n - k + 1; ++i) {
qs[i].l = i;
qs[i].r = i + k - 1;
qs[i].id = i;
}
// MO
sort(qs + 1, qs + n - k + 2);
int curl = 1;
int curr = 0;
int las = 0;
int now = 0;
for (int t = 1; t <= n - k + 1; ++t) {
auto [l, r, id] = qs[t];
if (b[l] == b[r]) {
int ret = 0;
for (int i = l; i <= r; ++i) {
++cnt2[a[i]];
if (cnt2[a[i]] > cnt2[ret])
ret = a[i];
}
c[id] = r - l + 1 - cnt2[ret];
for (int i = l; i <= r; ++i)
--cnt2[a[i]];
continue;
}
if (b[l] != las) {
while (curr > rs[b[l]])
--cnt[a[curr--]];
while (curl < rs[b[l]] + 1)
--cnt[a[curl++]];
now = 0;
las = b[l];
}
while (curr < r) {
++cnt[a[++curr]];
if (cnt[a[curr]] > cnt[now])
now = a[curr];
}
int ret = now;
while (curl > l) {
++cnt[a[--curl]];
if (cnt[a[curl]] > cnt[ret])
ret = a[curl];
}
c[id] = r - l + 1 - cnt[ret];
while (curl < rs[b[l]] + 1)
--cnt[a[curl++]];
}
// ST
for (int i = 1; i <= n - k + 1; ++i)
st[i][0] = i;
for (int j = 1; j <= 18; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n - k + 1; ++i) {
int x = st[i][j - 1];
int y = st[i + (1 << (j - 1))][j - 1];
if (c[x] < c[y])
st[i][j] = x;
else
st[i][j] = y;
}
}
// BLOCK: NEXT RAIL
for (int i = 1; i <= n - k + 1; ++i) {
b[i] = (i - 1) / block_size + 1;
rs[b[i]] = i;
}
for (int i = b[1]; i <= b[n - k + 1]; ++i)
ls[i] = rs[i - 1] + 1;
for (int i = 1; i <= n - k + 1; ++i) {
int l = i;
int r = n - k + 1;
int ret = n - k + 2;
while (l <= r) {
int mid = (l + r) >> 1;
if (c[query(i, mid)] < c[i]) {
ret = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
nxt[i] = ret;
}
for (int i = 1; i <= n - k + 1; ++i) {
int ret = i;
long long ans = 0;
while (ret <= rs[b[i]]) {
ans += (long long)(nxt[ret] - ret) * c[ret];
ret = nxt[ret];
}
rail[i] = ret;
w[i] = ans;
}
// QUERY
while (q--) {
int l, r;
cin >> l >> r;
r = r - k + 1;
int cur = l;
long long ans = 0;
while (rail[cur] <= r) {
ans += w[cur];
cur = rail[cur];
}
while (nxt[cur] <= r) {
ans += (long long)(nxt[cur] - cur) * c[cur];
cur = nxt[cur];
}
ans += (long long)(r - cur + 1) * c[cur];
cout << ans << endl;
}
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("CF2009G.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--)
solve();
cout << flush;
return 0;
}
// extreme version
#include <algorithm>
#include <iostream>
using namespace std;
const int block_size = 450;
const int lim = 2e5;
int lg[lim + 5];
int n, k, q;
int a[200005];
int b[200005];
int ls[200005];
int rs[200005];
int val[200005], len;
struct node {
int l, r, id;
inline bool operator<(const node &nd) const {
return (b[l] == b[nd.l]) ? ((b[l] & 1) ? (r < nd.r) : (r > nd.r)) : (b[l] < b[nd.l]);
}
} qs[200005];
int cnt[200005];
int cnt2[200005];
int c[200005];
int stk[200005], tail;
int st[200005][20];
static inline int query(int l, int r) {
int len = lg[r - l + 1];
int x = st[l][len];
int y = st[r - (1 << len) + 1][len];
return c[x] < c[y] ? x : y;
}
int pre[200005], nxt[200005];
long long f[200005], g[200005];
long long ans[200005];
static inline void solve() {
// READ & BLOCK & HASH
cin >> n >> k >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i] -= i;
val[++len] = a[i];
b[i] = (i - 1) / block_size + 1;
rs[b[i]] = i;
}
for (int i = b[1]; i <= b[n]; ++i)
ls[i] = rs[i - 1] + 1;
sort(val + 1, val + len + 1);
len = (int)(unique(val + 1, val + len + 1) - val - 1);
for (int i = 1; i <= n; ++i)
a[i] = (int)(lower_bound(val + 1, val + len + 1, a[i]) - val);
for (int i = 1; i <= len; ++i)
cnt[i] = 0;
// MO
{
int curl = 1;
int curr = 0;
int las = 0;
int now = 0;
for (int t = 1; t <= n - k + 1; ++t) {
int l = t;
int r = t + k - 1;
int id = t;
if (b[l] == b[r]) {
int ret = 0;
for (int i = l; i <= r; ++i) {
++cnt2[a[i]];
if (cnt2[a[i]] > cnt2[ret])
ret = a[i];
}
c[id] = r - l + 1 - cnt2[ret];
for (int i = l; i <= r; ++i)
--cnt2[a[i]];
continue;
}
if (b[l] != las) {
while (curr > rs[b[l]])
--cnt[a[curr--]];
while (curl < rs[b[l]] + 1)
--cnt[a[curl++]];
now = 0;
las = b[l];
}
while (curr < r) {
++cnt[a[++curr]];
if (cnt[a[curr]] > cnt[now])
now = a[curr];
}
int ret = now;
while (curl > l) {
++cnt[a[--curl]];
if (cnt[a[curl]] > cnt[ret])
ret = a[curl];
}
c[id] = r - l + 1 - cnt[ret];
while (curl < rs[b[l]] + 1)
--cnt[a[curl++]];
}
}
// PRE-PROCESS
for (int i = 1; i <= n - k + 1; ++i) {
b[i] = (i - 1) / block_size + 1;
st[i][0] = i;
}
for (int j = 1; j <= 18; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n - k + 1; ++i) {
int x = st[i][j - 1];
int y = st[i + (1 << (j - 1))][j - 1];
if (c[x] < c[y])
st[i][j] = x;
else
st[i][j] = y;
}
}
stk[++tail] = 0;
for (int i = 1; i <= n - k + 1; ++i) {
while (c[stk[tail]] > c[i])
--tail;
pre[i] = stk[tail];
stk[++tail] = i;
}
stk[tail = 1] = n - k + 2;
for (int i = n - k + 1; i; --i) {
while (c[stk[tail]] > c[i])
--tail;
nxt[i] = stk[tail];
stk[++tail] = i;
}
for (int i = 1; i <= n - k + 1; ++i)
f[i] = f[pre[i]] + (long long)c[i] * (i - pre[i]);
for (int i = n - k + 1; i; --i)
g[i] = g[nxt[i]] + (long long)c[i] * (nxt[i] - i);
// QUERY
for (int i = 1; i <= q; ++i) {
cin >> qs[i].l >> qs[i].r;
qs[i].r = qs[i].r - k + 1;
qs[i].id = i;
}
sort(qs + 1, qs + q + 1);
{
int curl = 1;
int curr = 0;
long long now = 0;
for (int i = 1; i <= q; ++i) {
auto [l, r, id] = qs[i];
while (curl > l) {
--curl;
int p = query(curl, curr);
now += g[curl] - g[p] + (long long)c[p] * (curr - p + 1);
}
while (curr < r) {
++curr;
int p = query(curl, curr);
now += f[curr] - f[p] + (long long)c[p] * (p - curl + 1);
}
while (curl < l) {
int p = query(curl, curr);
now -= g[curl] - g[p] + (long long)c[p] * (curr - p + 1);
++curl;
}
while (curr > r) {
int p = query(curl, curr);
now -= f[curr] - f[p] + (long long)c[p] * (p - curl + 1);
--curr;
}
ans[id] = now;
}
}
for (int i = 1; i <= q; ++i)
cout << ans[i] << endl;
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("CF2009G.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
for (int i = 2; i <= lim; ++i)
lg[i] = lg[i >> 1] + 1;
int T;
cin >> T;
while (T--)
solve();
cout << flush;
return 0;
}