根号算法杂记
根号算法杂记
序列分块
某些 DS 问题中需要维护一些离散的信息单位,一般是单点修改。
-
若维护的信息支持简单合并,则可以考虑用线段树、平衡树等区间数据结构。
-
若并不存在高效的信息合并化简方式,则可以考虑分块。
序列分块的基本思想是通过对原序列的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。
对于某些操作问题,若无法实时维护信息,则可以维护整块必要的信息,在查询散块时暴力重构。
P5356 [Ynoi2017] 由乃打扑克
维护序列 \(a_{1 \sim n}\) ,需要支持区间加和查询区间 \(k\) 大值。
\(n, m \le 10^5\)
考虑分块,维护每个块的有序数组。
修改时整块的有序数组不变,散块暴力重构即可,该部分时间复杂度 \(O(\frac{n}{B} + B \log B)\) 。
查询时二分答案,则需要在 \(O(\frac{n}{B})\) 个整块上二分和 \(O(B)\) 个散块上判定,可以事先将散块的元素归并以优化复杂度,时间复杂度 \(O(\frac{n}{B} \log B \log V + B)\) 。
取 \(B = 266\) ,需要一定的常数优化即可通过。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;
vector<pair<int, int> > vec[N];
int a[N], bel[N], L[N], R[N], tag[N];
int n, m, block, tot;
inline void prework() {
block = 266;
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
fill(bel + L[tot], bel + R[tot] + 1, tot);
for (int i = L[tot]; i <= R[tot]; ++i)
vec[tot].emplace_back(a[i], i);
sort(vec[tot].begin(), vec[tot].end());
if (R[tot] == n)
break;
}
}
inline void rebuild(int id, int l, int r, int k) {
for (int i = l; i <= r; ++i)
a[i] += k;
vector<pair<int, int> > v1, v2;
for (auto it : vec[id]) {
if (l <= it.second && it.second <= r)
v2.emplace_back(it.first + k, it.second);
else
v1.emplace_back(it.first, it.second);
}
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vec[id].begin());
}
inline void update(int l, int r, int k) {
if (bel[l] == bel[r]) {
rebuild(bel[l], l, r, k);
return;
}
rebuild(bel[l], l, R[bel[l]], k);
for (int i = bel[l] + 1; i < bel[r]; ++i)
tag[i] += k;
rebuild(bel[r], L[bel[r]], r, k);
}
inline int query(int l, int r, int k) {
if (k < 1 || k > r - l + 1)
return -1;
if (bel[l] == bel[r]) {
vector<int> vec(r - l + 1);
for (int i = l; i <= r; ++i)
vec[i - l] = a[i];
sort(vec.begin(), vec.end());
return vec[k - 1] + tag[bel[l]];
}
vector<int> now;
for (int i = l; i <= R[bel[l]]; ++i)
now.emplace_back(a[i] + tag[bel[l]]);
for (int i = L[bel[r]]; i <= r; ++i)
now.emplace_back(a[i] + tag[bel[r]]);
sort(now.begin(), now.end());
auto getrank = [&] (int l, int r, int k) {
int rnk = upper_bound(now.begin(), now.end(), k) - now.begin();
for (int i = bel[l] + 1; i < bel[r]; ++i) {
if (k - tag[i] >= vec[i].back().first)
rnk += R[i] - L[i] + 1;
else if (k - tag[i] >= vec[i].front().first)
rnk += upper_bound(vec[i].begin(), vec[i].end(), make_pair(k - tag[i], n + 1)) - vec[i].begin();
}
return rnk;
};
ll L = -2e9, R = 2e9, ans = -2e9;
while (L <= R) {
ll mid = (L + R) >> 1;
if (getrank(l, r, mid) >= k)
ans = mid, R = mid - 1;
else
L = mid + 1;
}
return ans;
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
prework();
while (m--) {
int op, l, r, k;
scanf("%d%d%d%d", &op, &l, &r, &k);
if (op == 1)
printf("%d\n", query(l, r, k));
else
update(l, r, k);
}
return 0;
}
P4135 作诗
查询区间内出现偶数次的数的个数,强制在线。
\(n, m \le 10^5\)
记 \(z_{i, x}\) 表示前 \(i\) 个块中 \(x\) 的出现次数,差分可得任意块区间中某个数的出现次数。
再记 \(s_{i, j}\) 表示第 \(i\) 块到第 \(j\) 块的合法数个数,这两者都可以 \(O(n \sqrt{n})\) 预处理。
查询时先整块直接用预处理出的信息,散块暴力枚举即可。
时间复杂度 \(O(n \sqrt{n} + m \sqrt{n})\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, B = 327;
bitset<N> vis, cnt;
int s[B][B], z[B][N];
int a[N], L[N], R[N], bel[N];
int n, c, m, block, tot;
inline void prework() {
block = sqrt(n);
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
fill(bel + L[tot], bel + R[tot] + 1, tot);
memcpy(z[tot] + 1, z[tot - 1] + 1, sizeof(int) * c);
for (int i = L[tot]; i <= R[tot]; ++i)
++z[tot][a[i]];
if (R[tot] == n)
break;
}
for (int i = 1; i <= tot; ++i) {
int res = 0;
for (int j = i; j <= tot; ++j) {
for (int k = L[j]; k <= R[j]; ++k) {
if (!vis.test(a[k]))
vis.set(a[k]), ++res;
res -= !cnt.test(a[k]), cnt.flip(a[k]), res += !cnt.test(a[k]);
}
s[i][j] = res;
}
vis.reset(), cnt.reset();
}
}
inline int query(int l, int r) {
if (bel[l] == bel[r]) {
int ans = 0;
for (int i = l; i <= r; ++i) {
if (!vis.test(a[i]))
vis.set(a[i]), ++ans;
ans -= !cnt.test(a[i]), cnt.flip(a[i]), ans += !cnt.test(a[i]);
}
for (int i = l; i <= r; ++i)
vis.reset(a[i]), cnt.reset(a[i]);
return ans;
}
int ans = s[bel[l] + 1][bel[r] - 1];
for (int i = l; i <= R[bel[l]]; ++i) {
if (!vis[a[i]]) {
vis.set(a[i]);
cnt.set(a[i], (z[bel[r] - 1][a[i]] - z[bel[l]][a[i]]) & 1);
if (z[bel[l]][a[i]] == z[bel[r] - 1][a[i]])
++ans;
}
ans -= !cnt.test(a[i]), cnt.flip(a[i]), ans += !cnt.test(a[i]);
}
for (int i = L[bel[r]]; i <= r; ++i) {
if (!vis[a[i]]) {
vis.set(a[i]);
cnt.set(a[i], (z[bel[r] - 1][a[i]] - z[bel[l]][a[i]]) & 1);
if (z[bel[l]][a[i]] == z[bel[r] - 1][a[i]])
++ans;
}
ans -= !cnt.test(a[i]), cnt.flip(a[i]), ans += !cnt.test(a[i]);
}
for (int i = l; i <= R[bel[l]]; ++i)
vis.reset(a[i]), cnt.reset(a[i]);
for (int i = L[bel[r]]; i <= r; ++i)
vis.reset(a[i]), cnt.reset(a[i]);
return ans;
}
signed main() {
scanf("%d%d%d", &n, &c, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
prework();
int lstans = 0;
while (m--) {
int l, r;
scanf("%d%d", &l, &r);
l = (l + lstans) % n + 1, r = (r + lstans) % n + 1;
if (l > r)
swap(l, r);
printf("%d\n", lstans = query(l, r));
}
return 0;
}
P8527 [Ynoi2003] 樋口円香
给出序列 \(a_{1 \sim n}\) 和 \(b_{1 \sim n}\) ,初始时 \(b_i = 0\) 。\(m\) 次操作,每次给出 \(l, r, L\) ,对于所有 \(k \in [l, r]\) ,将 \(b_{L + k - l}\) 增加 \(a_k \) 。最后输出 \(b_{1 \sim n}\) 。
\(n \le 10^5\) ,\(m \le 10^6\) ,\(0 \le a_i \le 1000\)
考虑分块,设块长为 \(B\) ,每次操作暴力处理散块,整块先存下来最后一起处理。
对于一个整块 \([l, r]\) ,记 \(c_i\) 表示这个块加到以 \(b_i\) 开始的区间的次数,\(d_i = a_{l + i} (0 \le i \le r - l)\) ,则 \(b_i \gets \sum_{j = 1}^i c_j d_{i - j}\) ,不难用 NTT 处理。注意 NTT 模数应选取为 \(1004535809\) ,因为卷积时答案上限为 \(10^9\) 。
时间复杂度 \(O(mB + \frac{n}{B} (n \log n + m))\) ,取 \(B = \sqrt{\frac{n^2 \log n + nm}{m}}\) 即可做到 \(O(n \sqrt{m \log n} + m \sqrt{n})\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7, B = 51, LOGN = 19;
int c[B][N], a[N], ans[N], L[N], R[N], bel[N];
int n, m, block, tot;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline void prework() {
block = 2048;
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(n, tot * block);
fill(bel + L[tot], bel + R[tot] + 1, tot);
if (R[tot] == n)
break;
}
}
namespace Poly {
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
const int Mod = 1004535809, rt = 3, invrt = (Mod + 1) / 3;
int iG[2][LOGN][N];
int inv[N], rev[N], a[N], b[N];
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline void prework() {
for (int i = 1, lgi = 0; i < N; i <<= 1, ++lgi) {
int val[2] = {mi(invrt, (Mod - 1) / (i << 1)), mi(rt, (Mod - 1) / (i << 1))};
iG[0][lgi][0] = iG[1][lgi][0] = 1;
for (int j = 1; j < i; ++j) {
iG[0][lgi][j] = 1ll * iG[0][lgi][j - 1] * val[0] % Mod;
iG[1][lgi][j] = 1ll * iG[1][lgi][j - 1] * val[1] % Mod;
}
}
}
inline int calc(int n) {
int len = 1;
while (len < n)
len <<= 1;
for (int i = 0; i < len; ++i)
rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? len >> 1 : 0);
return len;
}
inline void NTT(int *f, int n, int op) {
for (int i = 0; i < n; ++i)
if (i < rev[i])
swap(f[i], f[rev[i]]);
for (int k = 1, lgk = 0; k < n; k <<= 1, ++lgk)
for (int i = 0; i < n; i += k << 1)
for (int j = 0; j < k; ++j) {
int fl = f[i + j], fr = 1ll * iG[op == 1][lgk][j] * f[i + j + k] % Mod;
f[i + j] = add(fl, fr), f[i + j + k] = dec(fl, fr);
}
if (op == -1) {
int invn = mi(n, Mod - 2);
for (int i = 0; i < n; ++i)
f[i] = 1ll * f[i] * invn % Mod;
}
}
inline void Mul(int *f, int n, int *g, int m, int *res) {
int len = calc(n + m - 1);
cpy(a, f, n), clr(a + n, len - n);
cpy(b, g, m), clr(b + m, len - m);
NTT(a, len, 1), NTT(b, len, 1);
for (int i = 0; i < len; ++i)
a[i] = 1ll * a[i] * b[i] % Mod;
NTT(a, len, -1), cpy(res, a, len);
}
#undef cpy
#undef clr
} // namespace Poly
signed main() {
n = read();
for (int i = 1; i <= n; ++i)
a[i] = read();
m = read(), prework();
while (m--) {
int l = read(), r = read(), p = read();
if (bel[r] <= bel[l] + 1) {
for (int i = l; i <= r; ++i)
ans[p + i - l] += a[i];
} else {
for (int i = l; i <= R[bel[l]]; ++i)
ans[p + i - l] += a[i];
for (int i = bel[l] + 1; i < bel[r]; ++i)
++c[i][p + L[i] - l];
for (int i = L[bel[r]]; i <= r; ++i)
ans[p + i - l] += a[i];
}
}
Poly::prework();
for (int i = 1; i <= tot; ++i) {
Poly::Mul(c[i], n + 1, a + L[i], R[i] - L[i] + 1, c[i]);
for (int j = 1; j <= n; ++j)
ans[j] += c[i][j];
}
for (int i = 1; i <= n; ++i)
printf("%d\n", ans[i]);
return 0;
}
P11831 [省选联考 2025] 追忆
给出一张有向图,每条边 \((u, v)\) 均满足 \(u < v\) 。给出两个排列 \(a_{1 \sim n}\) 和 \(b_{1 \sim n}\) ,\(q\) 次操作,操作有:
1 x y
:交换 \(a_x\) 和 \(a_y\) 。2 x y
:交换 \(b_x\) 和 \(b_y\) 。3 x l r
:求所有满足 \(x\) 可以到达 \(y\) 且 \(a_y \in [l, r]\) 的 \(y\) 中 \(b_y\) 的最大值。\(n, q \le 10^5\) ,\(m \le 2 \times 10^5\)
首先处理点对之间的可达性问题,由于该图是一个 DAG,因此可以采用 bitset
,每次将 \(u\) 的 bitset
或上 \(v\) 的 bitset
得到,时间复杂度 \(O(\frac{nm}{\omega})\) 。
考虑对 \(a_{1 \sim n}\) 的值域分块,维护每个 \(a\) 对应的点。设块长为 \(B\) ,每块 \([l, r]\) 开一个 bitset
维护 \(a_u \in [l, r]\) 的点,则不难做到单次 \(O(1)\) 修改和区间 \(O(\frac{n}{B} \times \frac{n}{\omega} + B)\) 查询。
考虑询问,先考虑拿出合法的点,即 \(x\) 的可达点的 bitset
与上 \(a_y \in [l, r]\) 的 bitset
。暴力的想法是降序枚举 \(b\) ,然后每次查询是否存在合法点 \(y\) 满足 \(b_y\) 为当前枚举到的值。单次查询复杂度 \(O(n)\) ,无法接受。
一个优化想法是二分答案判定,需要区间查询 \(b\) 值域上一段后缀的对应点集。此时可以考虑套用刚才的分块结构,对 \(b\) 进行同样的分块维护,则时间复杂度降为 \(O((\frac{n}{B} \times \frac{n}{\omega} + B) \log n)\) 。
事实上可以去掉这个 \(\log n\) ,发现每次二分答案完全没有利用分块的性质。考虑查询时降序枚举 \(b\) 的块,若当前块内的点与合法点有交,则再暴力遍历当前块中的点找到交集中 \(b\) 最大的点即可,该部分复杂度可以做到 \(O(\frac{n}{B} \times \frac{n}{\omega} + B)\) 。
取 \(B = \frac{n}{\sqrt{\omega}}\) ,时间复杂度 \(O(\frac{nm}{\omega} + \frac{nq}{\sqrt{\omega}})\) ,由于常数小实际效率不错。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, S = 1.25e4 + 7;
struct Graph {
vector<int> e[N];
inline void clear(int n) {
for (int i = 1; i <= n; ++i)
e[i].clear();
}
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
struct Node {
int op, x, l, r;
} nd[N];
bitset<N> e[N];
int bel[N], L[N], R[N];
int n, m, q, block, tot;
inline void prework() {
block = max(n / 8, 1), tot = 0;
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
fill(bel + L[tot], bel + R[tot] + 1, tot);
if (R[tot] == n)
break;
}
}
struct Solver {
bitset<N> bit[S];
int val[N], id[N];
inline void insert(int x) {
bit[bel[x]].set(id[x]);
}
inline void remove(int x) {
bit[bel[x]].reset(id[x]);
}
inline void prework() {
for (int i = 1; i <= tot; ++i)
bit[i].reset();
for (int i = 1; i <= n; ++i)
id[val[i]] = i, insert(val[i]);
}
inline bitset<N> query(int l, int r) {
bitset<N> res;
res.reset();
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i)
res.set(id[i]);
} else {
for (int i = l; i <= R[bel[l]]; ++i)
res.set(id[i]);
for (int i = bel[l] + 1; i < bel[r]; ++i)
res |= bit[i];
for (int i = L[bel[r]]; i <= r; ++i)
res.set(id[i]);
}
return res;
}
inline void update(int x, int y) {
remove(val[x]), remove(val[y]);
swap(val[x], val[y]), id[val[x]] = x, id[val[y]] = y;
insert(val[x]), insert(val[y]);
}
} A, B;
int main() {
int testid, T;
scanf("%d%d", &testid, &T);
while (T--) {
scanf("%d%d%d", &n, &m, &q);
G.clear(n);
for (int i = 1; i <= m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v);
}
for (int u = n; u; --u) {
e[u].reset(), e[u].set(u);
for (int v : G.e[u])
e[u] |= e[v];
}
for (int i = 1; i <= n; ++i)
scanf("%d", &A.val[i]);
for (int i = 1; i <= n; ++i)
scanf("%d", &B.val[i]);
prework(), A.prework(), B.prework();
for (int i = 1; i <= q; ++i) {
scanf("%d", &nd[i].op);
if (nd[i].op == 3)
scanf("%d%d%d", &nd[i].x, &nd[i].l, &nd[i].r);
else
scanf("%d%d", &nd[i].l, &nd[i].r);
}
for (int i = 1; i <= q; ++i) {
if (nd[i].op == 1)
A.update(nd[i].l, nd[i].r);
else if (nd[i].op == 2)
B.update(nd[i].l, nd[i].r);
else {
bitset<N> now = e[nd[i].x] & A.query(nd[i].l, nd[i].r);
if (now.none()) {
puts("0");
continue;
}
int p = 0;
for (int j = bel[n]; j; --j)
if ((B.bit[j] & now).any()) {
p = j;
break;
}
for (int j = R[p]; j >= L[p]; --j)
if (now.test(B.id[j])) {
printf("%d\n", j);
break;
}
}
}
}
return 0;
}
DFS 模板题
给定一棵 \(n\) 个节点的无根树,记树上与点 \(u\) 相邻的点为 \(e_{u, 0 \sim k_u - 1}\)。同时每个节点 \(u\) 有一个初始激活编号 \(0 \le t_u < k_u\)。
\(q\) 次操作,操作有:
C u x
:修改 \(t_u\) 为 \(x\) 。Q x
:询问初始 \(u = 1\)、进行 \(x\) 次 \(p \gets u\) 、\(u \gets e_{u, t_u}\) 、\(t_p \gets (t_p + 1) \bmod k_p\) 操作后 \(u\) 的编号,注意询问并没有真正修改 \(t\) 数组。\(n, q \le 10^5\) ,ML = 32 MB
不妨设原树以节点 \(1\) 为根,对于点 \(u \ne 1\) 钦定 \(e_{u, k_u - 1}\) 为其父亲,只要将出边与 \(t\) rotate
一下即可。
考虑从 \(1\) 出发到后首次返回 \(1\) 之间的一轮:
-
把每条无向边视为两条有向边,则每条有向边至多被经过一次。
-
在本轮被经过的有向边下一轮仍会被经过。
-
该过程中经过的节点 \(u\) 在返回后有 \(t_u = 0\) 。
考虑包含 \(1\) 的满足 \(t_u = 0\) 的极大连通块,其中每个节点的儿子节点在一轮“出发-返回”后都会加入该连通块。故只要模拟 \(O(n^2)\) 步后就有 \(\forall 1 \le u \le n, t_u = 0\),此时每轮“出发-返回”相当于按欧拉序遍历每条边,不难得到 \(O(qn^2)\) 的模拟算法。
考虑维护每条有向边在第几轮“出发-返回”被第一次遍历,每次修改操作相当于将欧拉序中一个区间里的值 \(\pm 1\) 。
考虑分块,块长为 \(B\) ,则可以做到 \(O(B)\) 修改、\(O(\frac{n}{B})\) 回答前 \(x\) 轮“出发-返回”需要走多少步。询问时二分答案即可做到时间复杂度 \(O((n + q) (B + \frac{n}{B} \log n))\) ,取 \(B = \sqrt{n \log n}\) 即可做到 \(O((n + q) \sqrt{n \log n})\) 。
注意到欧拉序上相邻的边原图也相邻,故被第一次遍历到的轮数至多相差一轮,可以由此做到空间 \(O(n)\)。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
int k[N], t[N], faid[N], fa[N], seq[N], in[N], out[N], tim[N];
int n, q, tot, B;
void dfs1(int u, int f) {
fa[u] = f;
for (int v : G.e[u])
if (v != f)
dfs1(v, u);
}
void dfs2(int u, int d) {
seq[in[u] = out[u] = ++tot] = u, tim[tot] = d;
for (int i = 0; i < G.e[u].size() - (u != 1); ++i) {
int v = G.e[u][i];
dfs2(v, d + (i < t[u])), seq[out[v] = ++tot] = u, tim[tot] = d + (i < t[u]);
}
}
namespace FK {
const int B1 = 1.1e2 + 7, B2 = 1.9e3 + 7;
ll sum[B1][B2];
int cnt[B1][B2], mn[B1], mx[B1], tag[B1];
int L[B1], R[B1], bel[N];
int B, tot;
inline void build(int x) {
for (int i = 0; i <= mx[x] - mn[x]; ++i)
cnt[x][i] = sum[x][i] = 0;
mn[x] = *min_element(tim + L[x], tim + R[x] + 1), mx[x] = *max_element(tim + L[x], tim + R[x] + 1);
for (int i = L[x]; i <= R[x]; ++i)
++cnt[x][tim[i] - mn[x]], sum[x][tim[i] - mn[x]] += tim[i];
for (int i = 1; i <= mx[x] - mn[x]; ++i)
cnt[x][i] += cnt[x][i - 1], sum[x][i] += sum[x][i - 1];
}
inline void prework() {
B = sqrt(n * __lg(n));
while(++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(n, tot * B);
fill(bel + L[tot], bel + R[tot] + 1, tot);
build(tot);
if (R[tot] == n)
break;
}
}
inline void update(int l, int r, int k) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i)
tim[i] += k;
build(bel[l]);
} else {
for (int i = l; i <= R[bel[l]]; ++i)
tim[i] += k;
build(bel[l]);
for (int i = bel[l] + 1; i < bel[r]; ++i)
tag[i] += k;
for (int i = L[bel[r]]; i <= r; ++i)
tim[i] += k;
build(bel[r]);
}
}
pair<int, ll> query(int k) {
pair<int, ll> res = make_pair(0, 0ll);
for (int i = 1; i <= tot; ++i) {
if (k < mn[i] + tag[i])
continue;
res.first += cnt[i][min(k - tag[i], mx[i]) - mn[i]];
res.second += sum[i][min(k - tag[i], mx[i]) - mn[i]] +
1ll * tag[i] * cnt[i][min(k - tag[i], mx[i]) - mn[i]];
}
return res;
}
inline int find(int k, int s) {
for (int i = 1; i <= tot; ++i) {
if (k < mn[i] + tag[i])
continue;
if (s <= cnt[i][min(k - tag[i], mx[i]) - mn[i]]) {
for (int j = L[i]; j <= R[i]; ++j) {
s -= (tim[j] + tag[i] <= k);
if (!s)
return j;
}
} else
s -= cnt[i][min(k - tag[i], mx[i]) - mn[i]];
}
return -1;
}
} // namespace FK
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d%d", k + i, t + i);
G.e[i].resize(k[i]);
for (int &it : G.e[i])
scanf("%d", &it);
}
dfs1(1, 0);
for (int i = 2; i <= n; ++i) {
faid[i] = find(G.e[i].begin(), G.e[i].end(), fa[i]) - G.e[i].begin();
t[i] = (t[i] - (faid[i] + 1) + k[i]) % k[i];
if (faid[i] + 1 < G.e[i].size())
rotate(G.e[i].begin(), G.e[i].begin() + faid[i] + 1, G.e[i].end());
}
dfs2(1, 1), n = tot - 1, FK::prework();
scanf("%d", &q);
while (q--) {
char op[2];
scanf("%s", op);
if (op[0] == 'C') {
int x, y;
scanf("%d%d", &x, &y);
if (x != 1)
y = (y - faid[x] - 1 + k[x]) % k[x];
if (y < t[x])
FK::update(in[G.e[x][y]], out[G.e[x][t[x] - 1]], -1);
else if (y > t[x])
FK::update(in[G.e[x][t[x]]], out[G.e[x][y - 1]], 1);
t[x] = y;
} else {
ll x;
scanf("%lld", &x);
int val = 0;
for (int i = 0; i <= FK::bel[n]; ++i)
val = max(val, FK::mx[i] + FK::tag[i]);
int l = 0, r = val, pos = 0;
while (l <= r) {
int mid = (l + r) >> 1;
auto res = FK::query(mid);
if (1ll * (mid + 1) * res.first - res.second <= x)
pos = mid, l = mid + 1;
else
r = mid - 1;
}
auto res = FK::query(pos);
x -= 1ll * (pos + 1) * res.first - res.second;
printf("%d\n", pos == val ? seq[x % n + 1] : seq[FK::find(pos + 1, x + 1)]);
}
}
return 0;
}
QOJ10723. Outer LIS
给定序列 \(a_{1 \sim n}\) ,每个位置有权值 \(w_{1 \sim n}\) 。\(q\) 次询问,每次给出区间 \([l, r]\) ,求不选 \([l, r]\) 下标内元素的所有 \(a\) 的不降子序列中权值和的最大值(带权 LIS)。
\(n, q \le 10^5\)
首先预处理 \(f_i, g_i\) 表示以 \(i\) 结尾/开头的带权 LIS,对于每个询问,答案可能来自于:
- \(f\) 的前缀最大值或 \(g\) 的后缀最大值。
- 选取 \(i \in [1, l - 1], j \in [r + 1, n]\) 满足 \(a_i \le a_j\) ,贡献为 \(f_i + g_j\) 。
前者是容易的,考虑后者。将序列按块长为 \(\sqrt{n}\) 分块,记总块数为 \(m\) ,预处理:
- \(pre_{i, j}\) :第 \([1, i]\) 块中满足 \(a \le j\) 的 \(\max f\) 。
- \(suf_{i, j}\) :第 \([i, m]\) 块中满足 \(a \ge j\) 的 \(\max g\) 。
- \(ans_{i, j}\) :第 \([1, i]\) 块与第 \([j, m]\) 块元素两两组合的最优解。
以上信息不难 \(O(n \sqrt{n})\) 预处理。
对于询问 \([l, r]\) ,记左右端点所在块为 \(x, y\) ,答案可能来自于以下部分:
- 第 \([1, x - 1]\) 块与第 \([y + 1, m]\) 块的元素拼接,即 \(ans_{x - 1, y + 1}\) 。
- 第 \(x\) 块中下标 \(< l\) 的零散元素与第 \([y + 1, m]\) 块的元素拼接,枚举零散元素后利用 \(suf\) 数组即可查询。
- 第 \(y\) 块中下标 \(> r\) 的零散元素与第 \([1, x - 1]\) 块的元素拼接,枚举零散元素后利用 \(pre\) 数组即可查询。
- 两块的零散元素两两组合,排序后双指针计算。在预处理阶段对每个块按 \(a\) 进行排序,双指针时直接筛掉不在范围内的元素即可不带 \(\log\) 。
时间复杂度 \(O((n + q) \sqrt{n})\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, B = 327;
vector<int> vec[B];
ll f[N], g[N], pref[N], sufg[N], pre[B][N], suf[B][N], ans[B][B];
int a[N], val[N], L[B], R[B], bel[N];
int n, q, block, tot;
namespace BIT {
ll c[N];
inline void update(int x, ll k) {
for (; x <= n; x += x & -x)
c[x] = max(c[x], k);
}
inline ll query(int x) {
ll res = 0;
for (; x; x -= x & -x)
res = max(res, c[x]);
return res;
}
} // namespace BIT
inline void prework() {
block = sqrt(n);
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(n, tot * block);
fill(bel + L[tot], bel + R[tot] + 1, tot);
for (int i = L[tot]; i <= R[tot]; ++i) {
pre[tot][a[i]] = max(pre[tot][a[i]], f[i]);
suf[tot][a[i]] = max(suf[tot][a[i]], g[i]);
}
for (int i = 2; i <= n; ++i)
pre[tot][i] = max(pre[tot][i], pre[tot][i - 1]);
for (int i = n - 1; i; --i)
suf[tot][i] = max(suf[tot][i], suf[tot][i + 1]);
vec[tot].resize(R[tot] - L[tot] + 1), iota(vec[tot].begin(), vec[tot].end(), L[tot]);
sort(vec[tot].begin(), vec[tot].end(), [](const int &x, const int &y) {
return a[x] < a[y];
});
if (R[tot] == n)
break;
}
for (int i = 2; i <= tot; ++i)
for (int j = 1; j <= n; ++j)
pre[i][j] = max(pre[i][j], pre[i - 1][j]);
for (int i = tot - 1; ~i; --i)
for (int j = 1; j <= n; ++j)
suf[i][j] = max(suf[i][j], suf[i + 1][j]);
for (int i = 1; i <= tot; ++i) {
memcpy(ans[i] + 1, ans[i - 1] + 1, sizeof(ll) * tot);
for (int j = i + 1; j <= tot; ++j)
for (int k = L[i]; k <= R[i]; ++k)
ans[i][j] = max(ans[i][j], f[k] + suf[j][a[k]]);
}
}
inline ll query(int l, int r) {
int x = bel[l], y = bel[r];
ll res = max(max(pref[l - 1], sufg[r + 1]), ans[x - 1][y + 1]);
for (int i = L[x]; i < l; ++i)
res = max(res, f[i] + suf[y + 1][a[i]]);
for (int i = r + 1; i <= R[y]; ++i)
res = max(res, pre[x - 1][a[i]] + g[i]);
ll mx = 0;
for (int i = 0, j = 0; i < vec[y].size(); ++i) {
while (j < vec[x].size() && a[vec[x][j]] <= a[vec[y][i]])
mx = max(mx, vec[x][j] < l ? f[vec[x][j]] : 0), ++j;
if (vec[y][i] > r)
res = max(res, mx + g[vec[y][i]]);
}
return res;
}
signed main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i)
scanf("%d%d", a + i, val + i);
for (int i = 1; i <= n; ++i)
BIT::update(a[i], f[i] = BIT::query(a[i]) + val[i]), pref[i] = max(pref[i - 1], f[i]);
memset(BIT::c + 1, 0, sizeof(ll) * n);
for (int i = n; i; --i)
BIT::update(n - a[i] + 1, g[i] = BIT::query(n - a[i] + 1) + val[i]), sufg[i] = max(sufg[i + 1], g[i]);
prework();
while (q--) {
int l, r;
scanf("%d%d", &l, &r);
printf("%lld\n", query(l, r));
}
return 0;
}
AT_joisc2016_h 回転寿司
给定一个 \(n\) 个点的环,第 \(i\) 个点的权值为 \(a_i\) 。
\(q\) 次询问,每次给出 \(s, t, k\) ,依次枚举 \(i = s \sim t\) ,若 \(a_i > k\) 则交换二者,求最终 \(k\) 的值,注意询问的影响会被保留。
\(n \le 4 \times 10^5\) ,\(q \le 2.5 \times 10^4\)
考虑分块,设块长为 \(B\) 。
显然一个 \(k\) 经过一整块后会变成 \(k\) 与整块的最大值,若只考虑整块,则每个位置的具体值是无需关心的,只要用一个堆维护最大值即可,该部分时间复杂度 \(O(q \frac{n}{B} \log B)\) 。
对于散块的修改是简单的,直接暴力做就好了,该部分时间复杂度 \(O(qB)\) 。但是由于之前查询整块时没有维护每个位置的值,无法直接查询,因此需要考虑求出每个位置的值。
考虑对每个块维护新插入的数字,每次直接对这个块重构。但是直接重构复杂度是 \(O(q B^2)\) 的,手动模拟可以发现插入的数的顺序对序列的值不影响,因此考虑按数字大小插入,从左到右枚举块上的所有数,如果这个数大于当前最小值,就做一次交换操作,该部分时间复杂度 \(O(q B \log q)\) 。
取 \(B = \sqrt{n}\) ,时间复杂度 \(O(q \sqrt{n} \log q)\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 7, B = 637;
priority_queue<int> q1[B];
priority_queue<int, vector<int>, greater<int> > q2[B];
int a[N], L[N], R[N], bel[N];
int n, m, block, tot;
inline void prework() {
block = sqrt(n);
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(tot * block, n);
for (int i = L[tot]; i <= R[tot]; ++i)
bel[i] = tot, q1[tot].emplace(a[i]);
if (R[tot] == n)
break;
}
}
inline void rebuild(int x) {
if (q2[x].empty())
return;
for (int i = L[x]; i <= R[x]; ++i)
if (a[i] > q2[x].top()) {
int k = q2[x].top();
q2[x].pop(), q2[x].emplace(a[i]), a[i] = k;
}
while (!q2[x].empty())
q2[x].pop();
}
inline int BruteForce(int x, int l, int r, int k) {
for (int i = l; i <= r; ++i)
if (a[i] > k)
swap(k, a[i]);
while (!q1[x].empty())
q1[x].pop();
for (int i = L[x]; i <= R[x]; ++i)
q1[x].emplace(a[i]);
return k;
}
inline int query(int l, int r, int k) {
if (bel[l] == bel[r])
return rebuild(bel[l]), BruteForce(bel[l], l, r, k);
else {
rebuild(bel[l]), k = BruteForce(bel[l], l, R[bel[l]], k);
for (int i = bel[l] + 1; i < bel[r]; ++i) {
int x = q1[i].top();
if (x > k)
q1[i].pop(), q1[i].emplace(k), q2[i].emplace(k), k = x;
}
return rebuild(bel[r]), BruteForce(bel[r], L[bel[r]], r, k);
}
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
prework();
while (m--) {
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
printf("%d\n", l <= r ? query(l, r, k) : query(1, r, query(l, n, k)));
}
return 0;
}
撒关键点
P11587 [KTSC 2022 R2] 编程测试
给定 \(a_{1 \sim n}\) 和 \(b_{1 \sim n - 1}\) ,每个 \(b_i\) 可以选择 \(x \in [0, b_i]\) ,然后令 \(a_i \to a_i + x\) ,\(a_{i + 1} \to a_{i + 1} + b_i - x\) 。\(q\) 次询问 \(\min_{i = l}^r a_i\) 可能的最大值。
\(n, q \le 10^5\)
考虑二分答案 \(k\) ,问题可以转化为二分图匹配问题,考虑 Hall 定理,要求 \(\forall (x, y], k (y - x) \ge b_x + \sum_{i = x + 1}^y a_i + b_i\) 。
那么无需二分答案,答案即为 \(\min \lfloor \frac{(A_y + B_y) - (A_x + B_{x - 1})}{y - x} \rfloor\) ,其中 \(A, B\) 为前缀和。
不难发现固定 \(y\) 时最优的 \(x\) 一定在下凸壳上,直接维护下凸壳,每次二分即可。
接下来考虑多组询问,考虑分块,每隔 \(\sqrt{n}\) 放一个关键点,然后预处理每个关键点所有前后缀的答案。对于一组询问 \(l, r\) ,则还需处理左右散块之间的贡献,直接暴力即可。时间复杂度 \(O(q \sqrt{n} \log n)\) 。
考虑去掉 \(\log\) ,首先将这个斜率理解为平均值,假设 \(x_2 < x_1 < y_1 < y_2\) 且 \(y_1\) 的决策点在 \(x_1\) ,\(y_2\) 的决策点在 \(x_2\) ,则:
- \(f(x_1, y_1) < f(x_2, y_1)\) :这说明 \([x_2, x_1)\) 的平均数大于 \([x_1, y_1]\) 的平均数。
- \(f(x_2, y_2) < f(x_1, y_2)\) :这说明 \([x_2, x_1)\) 的平均数小于 \([x_1, y_2]\) 的平均数。
因此 \([x_1, y_1]\) 的平均数小于 \([x_1, y_2]\) 的平均数,此时 \(f(x_2, y_2) > f(x_1, y_1)\) ,也就是说可以不用考虑 \(y_2\) 的决策,因此只要考虑有决策单调性的 \(y\) 的决策即可。
时间复杂度 \(O(n \sqrt{n})\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 7, M = 317;
pair<int, ll> q[N];
ll A[N], B[N];
int f[M][N];
int n, m, block, head, tail;
inline bool check(pair<int, ll> a, pair<int, ll> b, pair<int, ll> c) {
return (c.second - a.second) * (c.first - b.first) <= 1ll * (c.second - b.second) * (c.first - a.first);
}
inline void insert(pair<int, ll> p) {
while (head < tail && check(q[tail - 1], q[tail], p))
--tail;
q[++tail] = p;
}
inline int query(pair<int, ll> p) {
if (head > tail)
return inf;
while (head < tail && check(q[head + 1], q[head], p))
++head;
return (p.second - q[head].second) / (p.first - q[head].first);
}
inline void prework(int x, int *f) {
head = 1, tail = 0, insert(make_pair(x, B[x])), f[x] = inf;
for (int i = x + 1; i <= n; ++i)
f[i] = min(f[i - 1], query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));
head = 1, tail = 0, insert(make_pair(-x, -A[x]));
for (int i = x - 1; ~i; --i)
f[i] = min(f[i + 1], query(make_pair(-i, -B[i]))), insert(make_pair(-i, -A[i]));
}
inline int solve(int l, int r) {
if (l / block == r / block) {
int ans = inf;
head = 1, tail = 0;
for (int i = l; i <= r; ++i)
ans = min(ans, query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));
return ans;
}
int ans = min(f[l / block + 1][r], f[r / block][l]);
head = 1, tail = 0;
for (int i = l; i < (l / block + 1) * block; ++i)
ans = min(ans, query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));
for (int i = r / block * block + 1; i <= r; ++i)
ans = min(ans, query(make_pair(i, A[i]))), insert(make_pair(i, B[i]));
return ans;
}
vector<int> testset(vector<int> a, vector<int> b, vector<int> ql, vector<int> qr) {
n = a.size(), m = ql.size(), block = sqrt(n);
for (int i = 1; i <= n; ++i) {
A[i] = A[i - 1] + a[i - 1] + (i == n ? 0 : b[i - 1]);
B[i] = B[i - 1] + a[i - 1] + (i == 1 ? 0 : b[i - 2]);
}
for (int i = 0; i * block <= n; ++i)
prework(i * block, f[i]);
vector<int> ans;
for (int i = 0; i < m; ++i)
ans.emplace_back(solve(ql[i], qr[i] + 1));
return ans;
}
操作分块
将操作每 \(B\) 块分一组:
- 对于块内修改涉及到的点,其对查询的贡献暴力统计。
- 对于其他点,用 DS 每次暴力预处理,查询时整体对 DS 查询。
最后做组内的修改即可。
P5443 [APIO2019] 桥梁
给定一张无向图,每条边有一个重量限制。\(q\) 次操作,操作有:
- 修改一条边的重量限制。
- 查询从某点出发,只经过重量限制 \(\ge w\) 的边能到达的点的数量。
\(n \le 5 \times 10^4\) ,\(m, q \le 10^5\)
考虑操作分块,先对询问按 \(w\) 排序。对于修改未涉及的边按重量限制排序,用并查集维护连通性,则枚举询问时是一个不断加入边的过程。对于修改涉及到的边,每次暴力判定是否合法,若合法则加入,处理完当前询问后撤销即可。
时间复杂度 \(O(\frac{q}{B} (m \log m + (m + B^2) \log n))\) ,\(B\) 取 \(\sqrt{m \log n}\) 比较快。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 7, M = 1e5 + 7;
struct Edge {
int u, v, w;
} e[M];
struct Node {
int t, x, k;
};
struct DSU {
int fa[N], siz[N], sta[N];
int top;
inline void prework(int n) {
iota(fa + 1, fa + n + 1, 1);
fill(siz + 1, siz + n + 1, 1);
top = 0;
}
inline int find(int x) {
while (x != fa[x])
x = fa[x];
return x;
}
inline void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y)
return;
if (siz[x] < siz[y])
swap(x, y);
siz[x] += siz[y], fa[y] = x, sta[++top] = y;
}
inline void restore(int k) {
while (top > k) {
int x = sta[top--];
siz[fa[x]] -= siz[x], fa[x] = x;
}
}
} dsu;
int flag[M];
int n, m, q;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i)
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
scanf("%d", &q);
int block = sqrt((m + 1) * __lg(n));
dsu.prework(n);
for (int qL = 1, qR; qL <= q; qL = qR + 1) {
qR = min(qL + block - 1, q);
vector<Node> upd, qry, ans;
for (int i = qL; i <= qR; ++i) {
int op, x, k;
scanf("%d%d%d", &op, &x, &k);
if (op == 1)
upd.emplace_back((Node){i, x, k}), flag[x] = e[x].w;
else
qry.emplace_back((Node){i, x, k});
}
sort(qry.begin(), qry.end(), [](const Node &a, const Node &b) {
return a.k > b.k;
});
vector<int> id(m);
iota(id.begin(), id.end(), 1);
sort(id.begin(), id.end(), [](const int &a, const int &b) {
return e[a].w > e[b].w;
});
auto pos = id.begin();
for (Node it : qry) {
for (; pos != id.end() && e[*pos].w >= it.k; ++pos)
if (!flag[*pos])
dsu.merge(e[*pos].u, e[*pos].v);
for (Node it2 : upd) {
if (it2.t <= it.t)
flag[it2.x] = it2.k;
else
break;
}
int lsttop = dsu.top;
for (Node it2 : upd)
if (flag[it2.x] >= it.k)
dsu.merge(e[it2.x].u, e[it2.x].v);
ans.emplace_back((Node) {it.t, 0, dsu.siz[dsu.find(it.x)]});
dsu.restore(lsttop);
for (Node it2 : upd)
flag[it2.x] = e[it2.x].w;
}
sort(ans.begin(), ans.end(), [](const Node &a, const Node &b) { return a.t < b.t; });
for (Node it : ans)
printf("%d\n", it.k);
for (Node it : upd)
e[it.x].w = it.k, flag[it.x] = 0;
dsu.restore(0);
}
return 0;
}
P11831 [省选联考 2025] 追忆
给出一张有向图,每条边 \((u, v)\) 均满足 \(u < v\) 。给出两个排列 \(a_{1 \sim n}\) 和 \(b_{1 \sim n}\) ,\(q\) 次操作,操作有:
1 x y
:交换 \(a_x\) 和 \(a_y\) 。2 x y
:交换 \(b_x\) 和 \(b_y\) 。3 x l r
:求所有满足 \(x\) 可以到达 \(y\) 且 \(a_y \in [l, r]\) 的 \(y\) 中 \(b_y\) 的最大值。\(n, q \le 10^5\) ,\(m \le 2 \times 10^5\)
首先处理点对之间的可达性问题,由于该图是一个 DAG,因此可以采用 bitset
,每次将 \(u\) 的 bitset
或上 \(v\) 的 bitset
得到,时间复杂度 \(O(\frac{nm}{\omega})\) 。
发现要维护的东西很难用 DS 维护,考虑操作分块,每 \(\omega\) 个操作分一组。在一组中将 \(y\) 按该组内修改是否涉及 \(y\) 分类。被修改的 \(y\) 可以暴力枚举计算,下面考虑未被修改的 \(y\) 的信息统计。
对于每个点 \(u\) ,设 \(f_{u, i}\) 表示第 \(i\) 个询问的 \(x\) 是否可以到达 \(u\) ,这不难单组 \(O(m)\) 预处理出。
再设 \(g_{u, i}\) 表示 \(u\) 是否在第 \(i\) 个询问的 \([l, r]\) 限制内,这不难单组差分 \(O(n)\) 求出。那么对于一个 \(u\) ,其只会对 \(f_u \cap g_{a_u}\) 的询问产生贡献。
按 \(b\) 降序枚举 \(u\) ,每次只要将 \(f_u \cap g_{a_u}\) 中未被赋上答案的询问赋上答案 \(b_u\) 即可。
总时间复杂度 \(O(\frac{nm + mq + nq}{\omega})\) ,实测块长设为 256 时可以通过。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, B = 256;
struct Graph {
vector<int> e[N];
inline void clear(int n) {
for (int i = 1; i <= n; ++i)
e[i].clear();
}
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
struct Node {
int op, x, l, r;
} nd[N];
bitset<N> reach[N];
bitset<B> f[N], g[N];
int a[N], b[N], id[N], ans[N];
bool upd[N];
int n, m, q;
signed main() {
int testid, T;
scanf("%d%d", &testid, &T);
while (T--) {
scanf("%d%d%d", &n, &m, &q);
G.clear(n);
for (int i = 1; i <= m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v);
}
for (int u = n; u; --u) {
reach[u].reset(), reach[u].set(u);
for (int v : G.e[u])
reach[u] |= reach[v];
}
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
for (int i = 1; i <= n; ++i)
scanf("%d", b + i);
for (int i = 1; i <= q; ++i) {
scanf("%d", &nd[i].op);
if (nd[i].op == 1 || nd[i].op == 2)
scanf("%d%d", &nd[i].l, &nd[i].r);
else
scanf("%d%d%d", &nd[i].x, &nd[i].l, &nd[i].r);
}
memset(ans + 1, 0, sizeof(int) * q);
for (int L = 1, R; L <= q; L = R + 1) {
R = min(L + B - 1, q);
for (int i = L; i <= R; ++i)
if (nd[i].op == 1 || nd[i].op == 2)
upd[nd[i].l] = upd[nd[i].r] = true;
vector<int> vec;
for (int u = 1; u <= n; ++u)
if (upd[u])
vec.emplace_back(u);
for (int i = 1; i <= n; ++i)
f[i].reset(), g[i].reset();
bitset<B> all = 0;
for (int i = L; i <= R; ++i)
if (nd[i].op == 3)
all.set(i - L), f[nd[i].x].set(i - L), g[nd[i].l].set(i - L), g[nd[i].r + 1].set(i - L);
for (int u = 1; u <= n; ++u) {
g[u] ^= g[u - 1], id[b[u]] = u;
for (int v : G.e[u])
f[v] |= f[u];
}
for (int i = n; i && all.any(); --i) {
int u = id[i];
if (upd[u])
continue;
bitset<B> now = all & f[u] & g[a[u]];
all ^= now;
for (int i = now._Find_first(); i != now.size(); i = now._Find_next(i))
ans[L + i] = b[u];
}
for (int i = L; i <= R; ++i) {
if (nd[i].op == 1)
swap(a[nd[i].l], a[nd[i].r]);
else if (nd[i].op == 2)
swap(b[nd[i].l], b[nd[i].r]);
else {
for (int u : vec)
if (reach[nd[i].x].test(u) && nd[i].l <= a[u] && a[u] <= nd[i].r)
ans[i] = max(ans[i], b[u]);
}
}
for (int i = L; i <= R; ++i)
if (nd[i].op == 1 || nd[i].op == 2)
upd[nd[i].l] = upd[nd[i].r] = false;
}
for (int i = 1; i <= q; ++i)
if (nd[i].op == 3)
printf("%d\n", ans[i]);
}
return 0;
}
事实上有一种更为优秀的操作分块方法,设块长为 \(S\) 。同样对于修改涉及到的点暴力贡献答案,考虑维护其他点。
考虑对 \(a\) 值域分块,维护每个值的对应点,块长为 \(B\) 。设 \(f_{x, i}\) 表示 \(x\) 的可达点 \(y\) 中满足 \(a_y\) 属于第 \(i\) 块的 \(y\) 的 \(b_y\) 的最大值,不难单组 \(O(\frac{nm}{B})\) 预处理。
查询时整块可以直接贡献,散块直接暴力贡献即可,单次查询复杂度 \(O(\frac{n}{B} + B)\) 。
时间复杂度 \(O(\frac{nm}{\omega} + \frac{q}{S} \times \frac{nm}{B} + q (S + \frac{n}{B} + B))\) ,取 \(S = B = n^{\frac{2}{3}}\) 可以做到 \(O(\frac{nm}{\omega} + n^{\frac{5}{3}})\) 。
#include <bits/stdc++.h>
typedef unsigned long long ull;
using namespace std;
const int N = 1e5 + 7, B = 5e1 + 7;
struct Graph {
vector<int> e[N];
inline void clear(int n) {
for (int i = 1; i <= n; ++i)
e[i].clear();
}
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
struct Node {
int op, x, l, r;
} nd[N];
bitset<N> reach[N];
int f[N][B];
int a[N], b[N], ans[N], L[N], R[N], bel[N], id[N];
bool upd[N];
int n, m, q, block, tot;
inline void prework() {
block = pow(n, 0.667), tot = 0;
while (++tot) {
L[tot] = R[tot - 1] + 1, R[tot] = min(n, block * tot);
fill(bel + L[tot], bel + R[tot] + 1, tot);
if (R[tot] == n)
break;
}
}
signed main() {
int testid, T;
scanf("%d%d", &testid, &T);
while (T--) {
scanf("%d%d%d", &n, &m, &q);
G.clear(n);
for (int i = 1; i <= m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v);
}
for (int u = n; u; --u) {
reach[u].reset(), reach[u].set(u);
for (int v : G.e[u])
reach[u] |= reach[v];
}
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
for (int i = 1; i <= n; ++i)
scanf("%d", b + i);
for (int i = 1; i <= q; ++i) {
scanf("%d", &nd[i].op);
if (nd[i].op == 1 || nd[i].op == 2)
scanf("%d%d", &nd[i].l, &nd[i].r);
else
scanf("%d%d%d", &nd[i].x, &nd[i].l, &nd[i].r);
}
memset(ans + 1, 0, sizeof(int) * q);
prework();
for (int qL = 1, qR; qL <= q; qL = qR + 1) {
qR = min(qL + block - 1, q);
for (int i = qL; i <= qR; ++i)
if (nd[i].op == 1 || nd[i].op == 2)
upd[nd[i].l] = upd[nd[i].r] = true;
vector<int> vec;
for (int u = 1; u <= n; ++u)
if (upd[u])
vec.emplace_back(u);
for (int u = n; u; --u) {
memset(f[u] + 1, 0, sizeof(int) * tot);
if (!upd[u])
f[u][bel[a[u]]] = b[u];
for (int v : G.e[u])
for (int i = 1; i <= tot; ++i)
f[u][i] = max(f[u][i], f[v][i]);
}
memset(id + 1, 0, sizeof(int) * n);
for (int u = 1; u <= n; ++u)
if (!upd[u])
id[a[u]] = u;
for (int i = qL; i <= qR; ++i) {
if (nd[i].op == 1)
swap(a[nd[i].l], a[nd[i].r]);
else if (nd[i].op == 2)
swap(b[nd[i].l], b[nd[i].r]);
else {
if (bel[nd[i].r] <= bel[nd[i].l] + 1) {
for (int j = nd[i].l; j <= nd[i].r; ++j)
if (id[j] && reach[nd[i].x].test(id[j]))
ans[i] = max(ans[i], b[id[j]]);
} else {
for (int j = nd[i].l; j <= R[bel[nd[i].l]]; ++j)
if (id[j] && reach[nd[i].x].test(id[j]))
ans[i] = max(ans[i], b[id[j]]);
for (int j = bel[nd[i].l] + 1; j < bel[nd[i].r]; ++j)
ans[i] = max(ans[i], f[nd[i].x][j]);
for (int j = L[bel[nd[i].r]]; j <= nd[i].r; ++j)
if (id[j] && reach[nd[i].x].test(id[j]))
ans[i] = max(ans[i], b[id[j]]);
}
for (int u : vec)
if (reach[nd[i].x].test(u) && nd[i].l <= a[u] && a[u] <= nd[i].r)
ans[i] = max(ans[i], b[u]);
}
}
for (int i = qL; i <= qR; ++i)
if (nd[i].op == 1 || nd[i].op == 2)
upd[nd[i].l] = upd[nd[i].r] = false;
}
for (int i = 1; i <= q; ++i)
if (nd[i].op == 3)
printf("%d\n", ans[i]);
}
return 0;
}
P11369 [Ynoi2024] 弥留之国的爱丽丝
给出一个有向图,每条边颜色为黑色或白色,最初所有边都是黑色。
\(q\) 次操作,操作有:
- 反转某条边的颜色。
- 查询 \(u\) 是否能只经过黑色边到达 \(v\) 。
\(n \le 5 \times 10^4\) ,\(m, q \le 10^5\)
考虑操作分块,设快长为 \(B\) 。设块内涉及到的点和边称为关键点、关键边,二者均为 \(O(B)\) 级别。
考虑先去掉关键边,对关键点预处理出两两之间可达性,将图缩为一个仅含有关键点的图。由于图不是 DAG,因此需要 Tarjan 缩点后按反拓扑序处理,用 bitset
优化处理出 \(g_{i, j}\) 表示关键点 \(i\) 是否能到达关键点 \(j\) ,该部分复杂度 \(O(n + m + \frac{mB}{\omega} + B^2)\) 。
问题转化为动态加边、查询可达性。动态加边直接加,查询可达性就用 bitset
优化 bfs(维护还没搜到的可达点),单次询问可以做到 \(O(\frac{B^2}{\omega})\) 。
时间复杂度 \(O(\frac{q}{B} \times (n + m + \frac{mB}{\omega} + B^2) + q \frac{B^2}{\omega})\) ,取 \(B = 128\) 即可通过。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 7, M = 1e5 + 7, B = 128, S = B << 1;
struct Graph {
vector<int> e[N];
inline void clear(int n) {
for (int i = 1; i <= n; ++i)
e[i].clear();
}
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G, nG;
struct Edge {
int u, v, c;
} e[M];
struct Node {
int op, x, y;
} nd[M];
bitset<S> f[N], g[N];
int id[N], dfn[N], low[N], sta[N], leader[N];
bool vis[M];
int n, m, q, dfstime, top, scc;
void Tarjan(int u) {
dfn[u] = low[u] = ++dfstime, sta[++top] = u;
for (int v : G.e[u]) {
if (!dfn[v])
Tarjan(v), low[u] = min(low[u], low[v]);
else if (!leader[v])
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
f[++scc].reset();
while (sta[top] != u)
leader[sta[top--]] = scc;
leader[sta[top--]] = scc;
}
}
inline bool query(int u, int v) {
bitset<S> reach, vis;
reach.set(u), vis.set();
while (!reach.test(v)) {
int x = (reach & vis)._Find_first();
if (x == vis.size())
return false;
vis.reset(x), reach |= g[x];
}
return true;
}
signed main() {
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= m; ++i)
scanf("%d%d", &e[i].u, &e[i].v), e[i].c = 1;
for (int i = 1; i <= q; ++i) {
scanf("%d%d", &nd[i].op, &nd[i].x);
if (nd[i].op == 2)
scanf("%d", &nd[i].y);
}
for (int L = 1, R; L <= q; L = R + 1) {
R = min(L + B - 1, q);
memset(id + 1, -1, sizeof(int) * n);
memset(vis + 1, false, sizeof(bool) * m);
vector<int> key, edg;
for (int i = L; i <= R; ++i) {
if (nd[i].op == 1) {
if (!vis[nd[i].x])
edg.emplace_back(nd[i].x), vis[nd[i].x] = true;
if (id[e[nd[i].x].u] == -1)
id[e[nd[i].x].u] = key.size(), key.emplace_back(e[nd[i].x].u);
if (id[e[nd[i].x].v] == -1)
id[e[nd[i].x].v] = key.size(), key.emplace_back(e[nd[i].x].v);
} else {
if (id[nd[i].x] == -1)
id[nd[i].x] = key.size(), key.emplace_back(nd[i].x);
if (id[nd[i].y] == -1)
id[nd[i].y] = key.size(), key.emplace_back(nd[i].y);
}
}
G.clear(n);
for (int i = 1; i <= m; ++i)
if (e[i].c && !vis[i])
G.insert(e[i].u, e[i].v);
memset(dfn + 1, 0, sizeof(int) * n);
memset(low + 1, 0, sizeof(int) * n);
memset(leader + 1, 0, sizeof(int) * n);
dfstime = scc = 0;
for (int i = 1; i <= n; ++i)
if (!dfn[i])
Tarjan(i);
nG.clear(scc);
for (int u = 1; u <= n; ++u)
for (int v : G.e[u])
if (leader[u] != leader[v])
nG.insert(leader[u], leader[v]);
for (int i = 1; i <= n; ++i)
if (~id[i])
f[leader[i]].set(id[i]);
for (int u = 1; u <= scc; ++u)
for (int v : nG.e[u])
f[u] |= f[v];
for (int i = 0; i < key.size(); ++i) {
g[i].reset();
for (int j = 0; j < key.size(); ++j)
g[i].set(j, f[leader[key[i]]].test(j));
}
for (int i = L; i <= R; ++i) {
if (nd[i].op == 1)
e[nd[i].x].c ^= 1;
else {
vector<int> vec;
for (int it : edg)
if (e[it].c && !g[id[e[it].u]].test(id[e[it].v]))
vec.emplace_back(it), g[id[e[it].u]].set(id[e[it].v]);
puts(query(id[nd[i].x], id[nd[i].y]) ? "YES" : "NO");
for (int it : vec)
g[id[e[it].u]].reset(id[e[it].v]);
}
}
}
return 0;
}
根号平衡
P3396 哈希冲突
给定 \(a_{1 \sim n}\) ,\(m\) 次操作,操作有:
- 求所有模 \(y\) 为 \(x\) 的下标的数字和。
- 单点修改。
\(n, m \le 1.5 \times 10^5\)
考虑对模数根号分治:
- 对于 \(\le \sqrt{n}\) 的模数:修改时暴力把每个模数的答案修改过去,查询就直接查。
- 对于 \(> \sqrt{n}\) 的模数:修改时直接在上原序列改,查询时直接暴力跳下标。
时间复杂度 \(O(m \sqrt{n})\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1.5e5 + 7, B = 4e2 + 7;
int a[N], s[B][B];
int n, m;
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= n; ++i)
for (int j = 1; j < B; ++j)
s[j][i % j] += a[i];
while (m--) {
char op;
int x, y;
cin >> op >> x >> y;
if (op == 'A') {
if (x < B)
printf("%d\n", s[x][y]);
else {
int ans = 0;
for (int i = y; i <= n; i += x)
ans += a[i];
printf("%d\n", ans);
}
} else {
for (int i = 1; i < B; ++i)
s[i][x % i] += y - a[x];
a[x] = y;
}
}
return 0;
}
P8250 交友问题
给定一个 \(n\) 个点 \(m\) 条边的无向图,\(q\) 次询问,每次给出两个点 \(u, v\) ,求有多少与 \(u\) 相邻的点不与 \(v\) 相邻且不为 \(v\) 。
\(n, q \le 2 \times 10^5\) ,\(m \le 7 \times 10^5\)
设阈值为 \(B\) ,将所有度数 \(> B\) 的点称为重点,剩下点称为轻点,显然重点的个数不超过 \(\frac{2m - 2}{B}\) 。
用 bitset
存储重点的出边,用vector
和 set
存储轻点的出边。查询时分类讨论:
- 重点对重点:用
bitset
记录每个点的连边,查询时异或即可,时间复杂度 \(O(\frac{n}{w})\) 。 - 重点对轻点:先设答案为重点的度数,枚举轻点的所有出点判断即可,时间复杂度 \(O(B)\) 。
- 轻点对重点:枚举轻点的所有出点。判断是否是重点的出点即可,时间复杂度 \(O(B)\) 。
- 轻点对轻点:枚举 \(u\) 的所有出点判断是否为 \(v\) 的出点即可,时间复杂度 \(O(B \log n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7, S = 711;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
bitset<N> e1[S];
set<int> e2[N];
int id[N];
int n, m, q, tot;
signed main() {
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v), G.insert(v, u);
}
int B = sqrt(m * 2);
for (int i = 1; i <= n; ++i) {
if (G.e[i].size() > B) {
id[i] = ++tot;
for (int it : G.e[i])
e1[id[i]].set(it);
} else {
for (int it : G.e[i])
e2[i].emplace(it);
}
}
while (q--) {
int u, v;
scanf("%d%d", &u, &v);
if (id[u] && id[v]) {
bitset<N> ans = e1[id[u]] ^ (e1[id[u]] & e1[id[v]]);
ans.reset(u), ans.reset(v);
printf("%d\n", ans.count());
} else if (id[u]) {
int ans = G.e[u].size();
for (int it : G.e[v])
if (it == u || it == v || e1[id[u]].test(it))
--ans;
printf("%d\n", ans);
} else if (id[v]) {
int ans = 0;
for (int it : G.e[u])
if (it != u && it != v && !e1[id[v]].test(it))
++ans;
printf("%d\n", ans);
} else {
int ans = 0;
for (int it : G.e[u])
if (it != u && it != v && e2[v].find(it) == e2[v].end())
++ans;
printf("%d\n", ans);
}
}
return 0;
}
[ARC052D] 9
给定 \(k, m\) ,求有多少个 \(n\) 满足 \(1 \le n \le m\) 且 \(n \equiv S(n) \pmod{k}\) ,其中 \(S(n)\) 表示 \(n\) 十进制下各位数字之和。
\(1 \le k, m \le 10^{10}\)
记阈值为 \(B\) :
- 当 \(k \ge B\) 时,因为最大的 \(S(n) \le 9 \times 10 = 90\) ,于是可以枚举数字和 \(S\) ,由于 \(n \bmod{k} = S\) 的 \(n\) 的个数不会超过 \(\lfloor \frac{m}{k} \rfloor + 1\) 个,直接暴力即可,时间复杂度 \(O(90 \times \frac{m}{k})\) 。
- 当 \(k < B\) 时,设 \(f_{i, j, s, 0/1}\) 表示考虑到从高到低的第 \(i\) 位,此时数字模 \(k\) 为 \(j\) ,数字和为 \(s\) ,是否已经小于 \(m\) 的数的个数,直接 DP 可以做到 \(O(10 \times 90 \times k \times 10) = O(9000k)\) 。
取 \(B = 10^4\) 即可。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
ll m, k;
namespace Method1 {
inline ll calc(ll x) {
ll res = 0;
while (x)
res += x % 10, x /= 10;
return res;
}
inline void solve() {
ll ans = 0;
for (ll s = 0; s <= 90; ++s)
for (ll i = s; i <= m; i += k)
if (calc(i) % k == s)
++ans;
printf("%lld", ans - 1);
}
} // namespace Method1
namespace Method2 {
ll f[11][10001][91][2];
inline vector<ll> getcount(ll x) {
vector<ll> res;
while (x)
res.push_back(x % 10), x /= 10;
return reverse(res.begin(), res.end()), res;
}
inline void solve() {
vector<ll> vec = getcount(m);
f[0][0][0][0] = 1;
for (ll i = 0; i < vec.size(); ++i)
for (ll j = 0; j < k; ++j)
for (ll s = 0; s <= 90; ++s) {
for (ll t = 0; t < vec[i]; ++t)
if (s + t <= 90)
f[i + 1][(j * 10 + t) % k][s + t][1] += f[i][j][s][0];
if (s + vec[i] <= 90)
f[i + 1][(j * 10 + vec[i]) % k][s + vec[i]][0] += f[i][j][s][0];
for (ll t = 0; t < 10; ++t)
if (s + t <= 90)
f[i + 1][(j * 10 + t) % k][s + t][1] += f[i][j][s][1];
}
ll ans = 0;
for (ll i = 0; i <= 90; ++i)
ans += f[vec.size()][i % k][i][0] + f[vec.size()][i % k][i][1];
printf("%lld", ans - 1);
}
} // namespace Method2
signed main() {
scanf("%lld%lld", &k, &m);
if (k >= 1e4)
Method1::solve();
else
Method2::solve();
return 0;
}
[ABC259Ex]Yet Another Path Counting
一个 \(n \times n\) 的网格图,每个格子上有颜色,求满足起点和终点颜色均相同且只向下和向右走的路径条数。
\(n \le 400\)
算法一:设起点为 \((x, y)\) ,终点为 \((z, w)\) ,则路径条数为 \(\binom{x - z + y - w}{x - z}\) 。对每种颜色枚举起点和终点,容易计算答案。
算法二:对于每种颜色 \(c\) 都做一遍,设 \(f_{i, j}\) 表示以 \((i, j)\) 结尾的合法路径条数,于是 \(f_{i, j} = f_{i, j} + f_{i, j - 1} + f_{i - 1, j}\) 。所有颜色为 \(c\) 位置 \((i, j)\) 初值为 \(1\) ,否则为 \(0\) 。
对于这种分颜色处理的问题,很经典的解决方式是对颜色出现次数进行根号分治
记阈值为 \(B\) ,则最多有 \(\frac{n^2}{B}\) 中颜色出现超过 \(B\) 次,对这部分 DP 剩余部分用组合数计算。
时间复杂度 \(O(nB^2 + \frac{n^4}{B})\) ,当 \(B = n\) 时取得最小值 \(O(n^3)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 8e2 + 7;
vector<pair<int, int> > p[N * N];
int a[N][N], f[N][N], fac[N], inv[N], invfac[N];
int n, ans;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int C(int n, int m) {
return m > n ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
signed main() {
scanf("%d", &n), prework(n * 2);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
scanf("%d", a[i] + j), p[a[i][j]].emplace_back(i, j);
int ans = 0;
for (int c = 1; c <= n * n; ++c) {
if (p[c].size() <= n) {
for (auto x : p[c])
for (auto y : p[c])
if (x.first <= y.first && x.second <= y.second)
ans = add(ans, C(y.first - x.first + y.second - x.second, y.first - x.first));
} else {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (a[i][j] == c)
ans = add(ans, f[i][j] = add(f[i - 1][j], add(f[i][j - 1], 1)));
else
f[i][j] = add(f[i - 1][j], f[i][j - 1]);
}
}
}
printf("%d", ans);
return 0;
}
CF1039D You Are Given a Tree
给出一棵树,对每个 \(k \in [1, n]\) ,求出最多能找出多少条不交的 \(k\) 个点的链。
\(n \le 10^5\)
设阈值为 \(B\) 。
对于 \(k \le B\) ,直接暴力做树上 DP,时间复杂度 \(O(nB)\) 。
对于 \(k \in [B + 1, n]\) ,则答案 \(\le \frac{n}{B}\) ,且答案单调不升。考虑枚举答案,二分找到这个答案对应的 \(k\) 的范围,时间复杂度 \(O(\frac{n}{B} \times n \log n)\) 。
取 \(B = \sqrt{n \log n}\) ,时间复杂度 \(O(n \sqrt{n \log n})\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
int fa[N], id[N], f[N], ans[N];
int n, dfstime;
void dfs(int u, int f) {
fa[u] = f, id[++dfstime] = u;
for (int v : G.e[u])
if (v != f)
dfs(v, u);
}
inline int solve(int k) {
fill(f + 1, f + n + 1, 1);
int res = 0;
for (int i = n; i; --i) {
int u = id[i];
if (fa[u] && ~f[u] && ~f[fa[u]]) {
if (f[u] + f[fa[u]] >= k)
++res, f[fa[u]] = -1;
else
f[fa[u]] = max(f[fa[u]], f[u] + 1);
}
}
return res;
}
signed main() {
scanf("%d", &n);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v), G.insert(v, u);
}
dfs(1, 0), ans[1] = n;
int B = sqrt(n * __lg(n) + 1);
for (int i = 2; i <= B; ++i)
ans[i] = solve(i);
for (int i = B + 1; i <= n; ++i) {
int res = solve(i), l = i, r = n, mxr = i;
while (l <= r) {
int mid = (l + r) >> 1;
if (solve(mid) == res)
mxr = mid, l = mid + 1;
else
r = mid - 1;
}
fill(ans + i, ans + mxr + 1, res), i = mxr;
}
for (int i = 1; i <= n; ++i)
printf("%d\n", ans[i]);
return 0;
}
P9994 [Ynoi Easy Round 2024] TEST_132
给出平面上的 \(n\) 个点,第 \(i\) 个点坐标为 \((x_i, y_i)\) ,初始权值为 \(v_i\) 。
\(m\) 次操作,操作有:
- 给出 \(X\) ,修改 \(x_i = X\) 的点的点权为 \(w_i^2\) 。
- 给出 \(Y\) ,查询 \(y_i = Y\) 的点的点权和 \(\bmod (10^9 + 7)\) 。
\(n, m \le 1.2 \times 10^6\) ,TL = 12s
考虑离线后对行的点数根号平衡处理。
对于点数 \(\le B\) 的行,修改可以暴力处理,这部分复杂度是 \(O(mB)\) 的。
对于点数 \(> B\) 的行,考虑询问时单独统计这些行,这部分复杂度是 \(O(\frac{nm}{B} \log V)\) 的,后面的 \(\log V\) 是快速幂的复杂度。
事实上快速幂的部分是可以优化成光速幂的,由于最终要求的形式为 \(v_i^{2^t \bmod (p - 1)}\) ,因此可以对每个 \(v_i\) 预处理其 \(2^0, 2^8, 2^{16}, 2^{24}\) 次幂的 \(0 \sim 2^8\) 次幂,即可做到 \(O(1)\) 查询。
取 \(B = \sqrt{n}\) ,时间复杂度 \(O(m \sqrt{n})\) ,块长调成 \(2000\) 跑的快些。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 1.2e6 + 7;
struct Node {
int op, x;
} nd[N];
vector<pair<int, int> > vec[N];
vector<int> qry[N];
int pw[N], sum[N], ans[N], pre[N];
int n, m;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
namespace Pow {
const int B = 1 << 8;
int pw[4][B];
inline void prework(int k) {
for (int i = 0; i < 4; k = 1ll * k * pw[i++][B - 1] % Mod) {
pw[i][0] = 1;
for (int j = 1; j < B; ++j)
pw[i][j] = 1ll * pw[i][j - 1] * k % Mod;
}
}
inline int query(int k) {
return 1ll * pw[3][k >> 24] * pw[2][k >> 16 & 255] % Mod * pw[1][k >> 8 & 255] % Mod * pw[0][k & 255] % Mod;
}
} // namespace Pow
signed main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) {
int x = read(), y = read(), k = read();
vec[x].emplace_back(y, k);
}
int B = 2e3;
for (int i = 1; i <= n; ++i)
if (vec[i].size() <= B) {
for (auto it : vec[i])
sum[it.first] = add(sum[it.first], it.second);
}
for (int i = 1; i <= m; ++i) {
nd[i].op = read(), nd[i].x = read();
if (nd[i].op == 1) {
if (vec[nd[i].x].size() <= B) {
for (auto &it : vec[nd[i].x]) {
sum[it.first] = dec(sum[it.first], it.second);
sum[it.first] = add(sum[it.first], it.second = 1ll * it.second * it.second % Mod);
}
}
} else
ans[i] = sum[nd[i].x], qry[nd[i].x].emplace_back(i);
}
pw[0] = 1;
for (int i = 1; i <= m; ++i)
pw[i] = 2ll * pw[i - 1] % (Mod - 1);
for (int i = 1; i <= n; ++i) {
if (vec[i].size() <= B)
continue;
for (int j = 1; j <= m; ++j)
pre[j] = pre[j - 1] + (nd[j].op == 1 && nd[j].x == i);
for (auto it : vec[i]) {
Pow::prework(it.second);
for (int x : qry[it.first])
ans[x] = add(ans[x], Pow::query(pw[pre[x]]));
}
}
for (int i = 1; i <= m; ++i)
if (nd[i].op == 2)
printf("%d\n", ans[i]);
return 0;
}
CF1793F Rebrending
给定 \(a_{1 \sim n}\) ,\(q\) 次询问,每次给出区间 \([l, r]\) ,求 \(\min_{l \le i < j \le r} |a_i - a_j|\) 。
\(a_i \le n \le 3 \times 10^5\) ,\(q \le 10^6\)
考虑将询问按右端点离线扫描线。
对于 \(r - l \le \sqrt{n}\) 的询问,考虑动态维护 \(f_i\) 表示 \([i, r]\) 的答案,显然只要维护 \(r - i \le \sqrt{n}\) 的部分即可,每次暴力更新即可做到 \(O(n \sqrt{n} + q)\) 。
对于 \(r - l > \sqrt{n}\) 的询问,显然答案 \(\le \sqrt{n}\) 。考虑动态维护 \(g_i\) 表示 \([g_i, r]\) 答案 \(\le i\) 的下标最大的位置,显然只要拿出 \([a_i - \sqrt{n}, a_i + \sqrt{n}]\) 的数的最后一次出现的位置更新即可,查询时暴力双指针即可做到 \(O(n \sqrt{n} + q)\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 7, Q = 1e6 + 7;
vector<pair<int, int> > tmp[N], qry1[N], qry2[N];
int a[N], f[N], g[N], lst[N], ans[Q];
int n, q;
signed main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
int B = sqrt(n);
for (int i = 1; i <= q; ++i) {
int l, r;
scanf("%d%d", &l, &r);
if (r - l <= B)
qry1[r].emplace_back(l, i);
else
tmp[r - l].emplace_back(l, i);
}
for (int i = 1; i <= n; ++i)
for (auto it : tmp[i])
qry2[it.first + i].emplace_back(it);
fill(f + 1, f + n + 1, n);
for (int i = 1; i <= n; lst[a[i]] = i, ++i) {
for (int j = i - 1; j >= i - B; --j)
f[j] = min(f[j], min(f[j + 1], abs(a[i] - a[j])));
for (auto it : qry1[i])
ans[it.second] = f[it.first];
for (int j = max(1, a[i] - B); j <= min(n, a[i] + B); ++j)
g[abs(a[i] - j)] = max(g[abs(a[i] - j)], lst[j]);
for (int i = 1; i <= B; ++i)
g[i] = max(g[i], g[i - 1]);
int j = B;
for (auto it : qry2[i]) {
while (j && g[j - 1] >= it.first)
--j;
ans[it.second] = j;
}
}
for (int i = 1; i <= q; ++i)
printf("%d\n", ans[i]);
return 0;
}
块状数组
主要是支持区间加-区间求和,一般有两种:
- \(O(\sqrt{n}) - O(1)\) 。
- \(O(1) - O(\sqrt{n})\) 。
当修改和查询不同阶时可以用于平衡复杂度。
P6782 [Ynoi2008] rplexq
给定一棵树,\(m\) 次询问,每次给出 \(l, r, x\) ,求有多少二元组 \((i, j)\) 满足 \(l \le i < j \le r\) 且 \(\mathrm{LCA}(i, j) = x\) 。
\(n, m \le 2 \times 10^5\) ,ML = 128MB
先将问题转化为求 \(\frac{1}{2}(f^2(x, l, r) - [x \in [l, r]] - \sum_{y \in son(x)} f^2(y, l, r))\) ,其中 \(f(x, l, r)\) 表示 \(x\) 子树内 \(\in [l, r]\) 的点数。
考虑将每个点 \(x\) 的儿子中 \(siz\) 前 \(\min(|son(x)|, \sqrt{n})\) 大的儿子拿出来,直接暴力将 \([l, r]\) 挂在这些点上,则查询相当于二维数点。由于修改数量为 \(O(n)\) 、询问数量为 \(O(m \sqrt{n})\) ,因此考虑用 \(O(\sqrt{n}) - O(1)\) 的分块维护,该部分复杂度为 \(O((n + m) \sqrt{n})\) 。
但是直接存储询问空间会炸,考虑优化删去儿子这部分的查询。发现每次查询的 dfn 区间连续且互不相交,因此查询完一个儿子后再将其挂到后面的儿子上,及时清空即可做到空间复杂度线性。
对于剩下的儿子,考虑把所有子树拿出来暴力跑莫队,不难发现拿出的总点数为 \(O(n)\) ,因此该部分复杂度为 \(O(n \sqrt{m})\) 。
由于第一部分跑得很满,而莫队跑得不满,因此可以适当将阈值减小以减小常数。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7, B = 57;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
struct Query {
ll ans;
int l, r, x, lst1, lst2, lim;
} qry[N];
ll ans[N];
int fa[N], siz[N], in[N], out[N], id[N];
int n, m, rt, dfstime;
void dfs1(int u, int f) {
fa[u] = f, siz[u] = 1;
for (int v : G.e[u])
if (v != f)
dfs1(v, u), siz[u] += siz[v];
}
void dfs2(int u) {
id[in[u] = ++dfstime] = u;
for (int v : G.e[u])
dfs2(v);
out[u] = dfstime;
}
namespace Method1 {
vector<int> qin[2][N], qout[2][N];
inline void insert(int id) {
int u = qry[id].x;
qin[0][in[u]].emplace_back(id);
qry[id].lim = out[G.e[u].size() <= B ? u : G.e[u][B - 1]];
}
namespace FK {
int s[N], tag[N];
int block;
inline void update(int x, int k) {
int bel = (x + block - 1) / block;
for (int i = x; i <= min(n, bel * block); ++i)
s[i] += k;
for (int i = bel + 1; i <= (n + block - 1) / block; ++i)
tag[i] += k;
}
inline int ask(int x) {
return s[x] + tag[(x + block - 1) / block];
}
inline int query(int l, int r) {
return ask(r) - ask(l - 1);
}
} // namespace FK
inline void solve() {
FK::block = sqrt(n);
for (int i = 1; i <= n; ++i) {
for (int it : qin[0][i]) {
qry[it].lst1 = FK::query(qry[it].l, qry[it].r);
qin[1][out[id[i]]].emplace_back(it);
if (i + 1 <= qry[it].lim)
qout[0][i + 1].emplace_back(it);
}
for (int it : qout[0][i]) {
qry[it].lst2 = FK::query(qry[it].l, qry[it].r);
qout[1][out[id[i]]].emplace_back(it);
}
qin[0][i].clear(), qin[0][i].shrink_to_fit();
qout[0][i].clear(), qout[0][i].shrink_to_fit();
FK::update(id[i], 1);
for (int it : qin[1][i]) {
int res = FK::query(qry[it].l, qry[it].r) - qry[it].lst1;
qry[it].ans += 1ll * res * res;
}
for (int it : qout[1][i]) {
int res = FK::query(qry[it].l, qry[it].r) - qry[it].lst2;
qry[it].ans -= 1ll * res * res;
if (i + 1 <= qry[it].lim)
qout[0][i + 1].emplace_back(it);
}
qin[1][i].clear(), qin[1][i].shrink_to_fit();
qout[1][i].clear(), qout[1][i].shrink_to_fit();
}
}
} // namespace Method1
namespace Method2 {
vector<int> q[N];
int seq[N], bel[N], cnt[N];
int tot;
inline void insert(int id) {
if (G.e[qry[id].x].size() > B)
q[qry[id].x].emplace_back(id);
}
void dfs(int u, int r) {
seq[++tot] = u, bel[u] = r;
for (int v : G.e[u])
dfs(v, r);
}
inline void solve() {
for (int x = 1; x <= n; ++x) {
if (G.e[x].size() <= B || q[x].empty())
continue;
seq[tot = 1] = 0;
for (int i = B; i < G.e[x].size(); ++i)
dfs(G.e[x][i], G.e[x][i]), cnt[G.e[x][i]] = 0;
seq[++tot] = n + 1;
sort(seq + 1, seq + tot + 1);
int block = sqrt(tot);
sort(q[x].begin(), q[x].end(), [&](const int &a, const int &b) {
int ida = qry[a].l / block, idb = qry[b].l / block;
return ida == idb ? (ida & 1 ? qry[a].r > qry[b].r : qry[a].r < qry[b].r) : ida < idb;
});
ll result = 0;
int l = 1, r = 0;
auto update = [&](int x, int k) {
if (!x || x == n + 1)
return;
result -= 1ll * cnt[bel[x]] * cnt[bel[x]];
cnt[bel[x]] += k;
result += 1ll * cnt[bel[x]] * cnt[bel[x]];
};
for (auto it : q[x]) {
while (seq[l] > qry[it].l)
update(seq[--l], 1);
while (seq[r] < qry[it].r)
update(seq[++r], 1);
while (seq[l] < qry[it].l)
update(seq[l++], -1);
while (seq[r] > qry[it].r)
update(seq[r--], -1);
qry[it].ans -= result;
}
}
}
} // namespace Method2
signed main() {
scanf("%d%d%d", &n, &m, &rt);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v), G.insert(v, u);
}
dfs1(rt, 0);
for (int i = 1; i <= n; ++i)
if (i != rt) {
G.e[i].erase(find(G.e[i].begin(), G.e[i].end(), fa[i]));
sort(G.e[i].begin(), G.e[i].end(), [](const int &a, const int &b) {
return siz[a] > siz[b];
});
}
dfs2(rt);
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &qry[i].l, &qry[i].r, &qry[i].x);
Method1::insert(i), Method2::insert(i);
}
Method1::solve(), Method2::solve();
for (int i = 1; i <= m; ++i)
printf("%lld\n", (qry[i].ans - (qry[i].l <= qry[i].x && qry[i].x <= qry[i].r)) / 2);
return 0;
}
值域倍增分块
P7447 [Ynoi2007] rgxsxrs
给定序列 \(a_{1 \sim n}\) ,\(m\) 次操作,操作有:
- 将区间内 \(> x\) 的数减去 \(x\) 。
- 查询区间和、最小值、最大值。
\(n, m \le 5 \times 10^5\) ,\(1 \le a_i, x \le 10^9\) ,强制在线,ML = 64 MB
考虑值域倍增分块,进制为 \(B\) ,则将值域 \(\in [B^i, B^{i + 1})\) 分在一个块内,对每一块建立线段树。
修改时再每一棵线段树上暴力,维护势能线段树:
- 若区间最大值 \(\le x\) ,直接退出。
- 若区间最小值减去 \(B^i\) 的值 \(\ge x\) ,直接打标记。
- 否则递归到叶子节点,就把这个叶子删除并丢到次数更低的块。
查询在每一块上面分别查询后合并即可。由于每个叶子只会下放 \(O(\log_B V)\) 次,每次插入都要 \(O(\log n)\) 的复杂度,因此时间复杂度为 \(O((n + m) \log_B V \log_2 n + m B \log_2 n \log_B V)\) ,空间复杂度 \(O(n \log_B V)\) 。
考虑优化空间,发现瓶颈在线段树,考虑线段树底层分块,取块长为 \(\log_B V\) 即可。
理论上取 \(B = e\) 最优,但是由于常数问题取 \(B = 32\) 表现比较优秀。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 5e5 + 7, B = 32, L = 6;
int a[N], bel[N], pw[L];
int n, m;
inline int getid(int x) {
return __lg(x) / __lg(B);
}
namespace SMT {
const int S = N << 4, M = 16;
ll s[S];
int rt[L], lc[S], rc[S], mn[S], mx[S], tag[S], cnt[S];
int tot;
inline void pushup(int x) {
s[x] = s[lc[x]] + s[rc[x]], cnt[x] = cnt[lc[x]] + cnt[rc[x]];
mn[x] = min(mn[lc[x]], mn[rc[x]]), mx[x] = max(mx[lc[x]], mx[rc[x]]);
}
inline void rebuild(int id, int x, int l, int r) {
mn[x] = inf, mx[x] = -inf, s[x] = cnt[x] = 0;
for (int i = l; i <= r; ++i) {
if (bel[i] != id)
continue;
mn[x] = min(mn[x], a[i]), mx[x] = max(mx[x], a[i]);
s[x] += a[i], ++cnt[x];
}
}
inline void spread(int x, int k) {
if (cnt[x])
s[x] += 1ll * cnt[x] * k, mn[x] += k, mx[x] += k, tag[x] += k;
}
inline void pushdown(int id, int x, int l, int r) {
if (r - l + 1 <= M) {
for (int i = l; i <= r; ++i)
if (bel[i] == id)
a[i] += tag[x];
} else
spread(lc[x], tag[x]), spread(rc[x], tag[x]);
tag[x] = 0;
}
void build(int id, int &x, int l, int r) {
x = ++tot;
if (r - l + 1 <= M) {
rebuild(id, x, l, r);
return;
}
int mid = (l + r) >> 1;
build(id, lc[x], l, mid), build(id, rc[x], mid + 1, r);
pushup(x);
}
void insert(int id, int x, int nl, int nr, int p, int k) {
if (tag[x])
pushdown(id, x, nl, nr);
if (nr - nl + 1 <= M) {
bel[p] = id, s[x] += k, ++cnt[x], mn[x] = min(mn[x], k), mx[x] = max(mx[x], k);
return;
}
int mid = (nl + nr) >> 1;
if (p <= mid)
insert(id, lc[x], nl, mid, p, k);
else
insert(id, rc[x], mid + 1, nr, p, k);
pushup(x);
}
void update(int id, int x, int nl, int nr, int l, int r, int k) {
if (!cnt[x] || mx[x] <= k)
return;
else if (l <= nl && nr <= r && mn[x] - k >= pw[id]) {
spread(x, -k);
return;
}
if (tag[x])
pushdown(id, x, nl, nr);
if (nr - nl + 1 <= M) {
l = max(nl, l), r = min(nr, r);
for (int i = l; i <= r; ++i)
if (bel[i] == id && a[i] > k) {
a[i] -= k;
if (a[i] < pw[id])
insert(getid(a[i]), rt[getid(a[i])], 1, n, i, a[i]);
}
rebuild(id, x, nl, nr);
return;
}
int mid = (nl + nr) >> 1;
if (l <= mid)
update(id, lc[x], nl, mid, l, r, k);
if (r > mid)
update(id, rc[x], mid + 1, nr, l, r, k);
pushup(x);
}
ll querysum(int id, int x, int nl, int nr, int l, int r) {
if (!cnt[x])
return 0;
else if (l <= nl && nr <= r)
return s[x];
if (tag[x])
pushdown(id, x, nl, nr);
if (nr - nl + 1 <= M) {
l = max(nl, l), r = min(nr, r);
ll res = 0;
for (int i = l; i <= r; ++i)
if (bel[i] == id)
res += a[i];
return res;
}
int mid = (nl + nr) >> 1;
if (r <= mid)
return querysum(id, lc[x], nl, mid, l, r);
else if (l > mid)
return querysum(id, rc[x], mid + 1, nr, l, r);
else
return querysum(id, lc[x], nl, mid, l, r) + querysum(id, rc[x], mid + 1, nr, l, r);
}
int querymin(int id, int x, int nl, int nr, int l, int r) {
if (!cnt[x])
return inf;
else if (l <= nl && nr <= r)
return mn[x];
if (tag[x])
pushdown(id, x, nl, nr);
if (nr - nl + 1 <= M) {
l = max(nl, l), r = min(nr, r);
int res = inf;
for (int i = l; i <= r; ++i)
if (bel[i] == id)
res = min(res, a[i]);
return res;
}
int mid = (nl + nr) >> 1;
if (r <= mid)
return querymin(id, lc[x], nl, mid, l, r);
else if (l > mid)
return querymin(id, rc[x], mid + 1, nr, l, r);
else
return min(querymin(id, lc[x], nl, mid, l, r), querymin(id, rc[x], mid + 1, nr, l, r));
}
int querymax(int id, int x, int nl, int nr, int l, int r) {
if (!cnt[x])
return -inf;
else if (l <= nl && nr <= r)
return mx[x];
if (tag[x])
pushdown(id, x, nl, nr);
if (nr - nl + 1 <= M) {
l = max(nl, l), r = min(nr, r);
int res = -inf;
for (int i = l; i <= r; ++i)
if (bel[i] == id)
res = max(res, a[i]);
return res;
}
int mid = (nl + nr) >> 1;
if (r <= mid)
return querymax(id, lc[x], nl, mid, l, r);
else if (l > mid)
return querymax(id, rc[x], mid + 1, nr, l, r);
else
return max(querymax(id, lc[x], nl, mid, l, r), querymax(id, rc[x], mid + 1, nr, l, r));
}
} // namespace SMT
inline void update(int l, int r, int k) {
for (int i = getid(k); i < L; ++i)
SMT::update(i, SMT::rt[i], 1, n, l, r, k);
}
inline ll querysum(int l, int r) {
ll res = 0;
for (int i = 0; i < L; ++i)
res += SMT::querysum(i, SMT::rt[i], 1, n, l, r);
return res;
}
inline int querymin(int l, int r) {
for (int i = 0; i < L; ++i) {
int res = SMT::querymin(i, SMT::rt[i], 1, n, l, r);
if (res != inf)
return res;
}
}
inline int querymax(int l, int r) {
for (int i = L - 1; ~i; --i) {
int res = SMT::querymax(i, SMT::rt[i], 1, n, l, r);
if (res != -inf)
return res;
}
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i), bel[i] = getid(a[i]);
pw[0] = 1;
for (int i = 1; i < L; ++i)
pw[i] = pw[i - 1] * B;
for (int i = 0; i < L; ++i)
SMT::build(i, SMT::rt[i], 1, n);
int lstans = 0;
while (m--) {
int op, l, r;
scanf("%d%d%d", &op, &l, &r);
l ^= lstans, r ^= lstans;
if (op == 1) {
int k;
scanf("%d", &k);
update(l, r, k ^ lstans);
} else {
ll res = querysum(l, r);
printf("%lld %d %d\n", res, querymin(l, r), querymax(l, r));
lstans = res & ((1 << 20) - 1);
}
}
return 0;
}