學習筆記:高階資料結構【2020落谷省選夏令營】
習題
方差
拆式子,維護支援求區間平方和和區間和的線段樹,記一個加法懶標記。
#include <iostream> #include <cstdio> using namespace std; const int N = 100005; int n, m; double a[N], tag[N << 2], s1[N << 2], s2[N << 2]; void inline pushup(int p) { s1[p] = s1[p << 1] + s1[p << 1 | 1]; s2[p] = s2[p << 1] + s2[p << 1 | 1]; } void pushdown(int p, int l, int mid, int r) { s2[p << 1] += (mid - l + 1) * tag[p] * tag[p] + 2 * s1[p << 1] * tag[p]; s2[p << 1 | 1] += (r - mid) * tag[p] * tag[p] + 2 * s1[p << 1 | 1] * tag[p]; s1[p << 1] += tag[p] * (mid - l + 1); s1[p << 1 | 1] += tag[p] * (r - mid); tag[p << 1] += tag[p], tag[p << 1 | 1] += tag[p]; tag[p] = 0; } void build(int p, int l, int r) { if (l == r) { s1[p] = a[r], s2[p] = a[r] * a[r]; return; } int mid = (l + r) >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); pushup(p); } void change(int p, int l, int r, int x, int y, double k) { if (x <= l && r <= y) { s2[p] += (r - l + 1) * k * k + 2 * s1[p] * k; s1[p] += (r - l + 1) * k; tag[p] += k; return; } int mid = (l + r) >> 1; pushdown(p, l, mid, r); if (x <= mid) change(p << 1, l, mid, x, y, k); if (mid < y) change(p << 1 | 1, mid + 1, r, x, y, k); pushup(p); } double query(int p, int l, int r, int x, int y, int o) { if (x <= l && r <= y) return o == 1 ? s1[p] : s2[p]; int mid = (l + r) >> 1; double res = 0; pushdown(p, l, mid, r); if (x <= mid) res += query(p << 1, l, mid, x, y, o); if (mid < y) res += query(p << 1 | 1, mid + 1, r, x, y, o); return res; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%lf", a + i); build(1, 1, n); while (m--) { int opt, x, y; scanf("%d%d%d", &opt, &x, &y); if (opt == 1) { double k; scanf("%lf", &k); change(1, 1, n, x, y, k); } else if (opt == 2) { printf("%.4f\n", query(1, 1, n, x, y, 1) / (y - x + 1)); } else { double s1 = query(1, 1, n, x, y, 1), s2 = query(1, 1, n, x, y, 2), p = s1 / (y - x + 1); printf("%.4f\n", ((y - x + 1) * p * p + s2 - 2 * s1 * p) / (y - x + 1)); } } return 0; }
[NOI2016]區間
按區間長度排序,雙指標,\([l, r]\) 符合條件等價於將 \([l, r]\) 的線段每段區間 + 1,單點最大值為 \(m\)
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int N = 500005, INF = 2e9; int n, m, d[N << 1], tot, val[N << 3], tag[N << 3]; struct Seg{ int l, r, len; bool operator < (const Seg &b) const { return len < b.len; } } s[N]; int inline get(int x) { return lower_bound(d + 1, d + 1 + tot, x) - d; } void inline pushup(int p) { val[p] = max(val[p << 1], val[p << 1 | 1]); } void inline pushdown(int p) { if (tag[p]) { val[p << 1] += tag[p], val[p << 1 | 1] += tag[p]; tag[p << 1] += tag[p], tag[p << 1 | 1] += tag[p]; tag[p] = 0; } } void change(int p, int l, int r, int x, int y, int k) { if (x <= l && r <= y) { val[p] += k, tag[p] += k; return ; } int mid = (l + r) >> 1; pushdown(p); if (x <= mid) change(p << 1, l, mid, x, y, k); if (mid < y) change(p << 1 | 1, mid + 1, r, x, y, k); pushup(p); } int main() { scanf("%d%d", &n, &m); for (int i = 1, l, r; i <= n; i++) { scanf("%d%d", &l, &r); s[i] = (Seg) { l, r, r - l }; d[++tot] = l, d[++tot] = r; } sort(s + 1, s + 1 + n); sort(d + 1, d + 1 + tot); tot = unique(d + 1, d + 1 + tot) - d - 1; for (int i = 1; i <= n; i++) s[i].l = get(s[i].l), s[i].r = get(s[i].r); int ans = INF; for (int i = 1, j = 1; i <= n; i++) { change(1, 1, tot, s[i].l, s[i].r, 1); while (j <= i && val[1] == m) { ans = min(ans, s[i].len - s[j].len); change(1, 1, tot, s[j].l, s[j].r, -1); ++j; } } if (ans == INF) puts("-1"); else printf("%d\n", ans); return 0; }
樓房重建
很妙的分治討論,通過劃分右子樹變成右左和右右,特殊的性質決定只用遞迴一層,
#include <iostream> #include <cstdio> using namespace std; const int N = 100005; int n, m, cnt[N << 2]; double val[N << 2]; // > v 的限制 int find(int p, double v, int l, int r) { if (l == r) return val[p] > v; int mid = (l + r) >> 1; if (val[p << 1] <= v) return find(p << 1 | 1, v, mid + 1, r); else return find(p << 1, v, l, mid) + cnt[p] - cnt[p << 1]; } void inline pushup(int p, int l, int mid, int r) { val[p] = max(val[p << 1], val[p << 1 | 1]); cnt[p] = cnt[p << 1] + find(p << 1 | 1, val[p << 1], mid + 1, r); } void change(int p, int l, int r, int x, int y) { if (l == r) { val[p] = (double)y / x, cnt[p] = 1; return; } int mid = (l + r) >> 1; if (x <= mid) change(p << 1, l, mid, x, y); else change(p << 1 | 1, mid + 1, r, x, y); pushup(p, l, mid, r); } int main() { scanf("%d%d", &n, &m); while (m--) { int x, y; scanf("%d%d", &x, &y); change(1, 1, n, x, y); printf("%d\n", cnt[1]); } }
[SCOI2015]情報傳遞
對於 \(x, y, c\),當前時刻為 \(t\),相當於求 \(t - c - 1\) 時刻前這條路徑上激活了多少點。
用樹狀陣列維護 \(d\),\(d_i\) 表示 \(i\) 到根路徑上的點權和,每次單點修改 \(\Rightarrow\) 子樹修改。
每次查詢用四個點差分。
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 200005;
int n, m, fa[N], rt, top[N], son[N], c[N];
int dfn[N], dfncnt, sz[N], dep[N], p[N], d[N], ans[N], T[N];
struct Q{
int x, y, p, id;
};
vector<Q> q[N];
int head[N], numE = 0;
struct E{
int next, v;
} e[N];
void inline addEdge(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
}
void dfs1(int u) {
sz[u] = 1;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u]) continue;
dep[v] = dep[u] + 1;
dfs1(v);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp) {
top[u] = tp, dfn[u] = ++dfncnt;
if (son[u]) dfs2(son[u], tp);
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int inline lca(int x, int y) {
while (top[x] != top[y]) {
while (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
void inline add(int x, int k) {
for (; x <= n; x += x & -x) c[x] += k;
}
int inline ask(int x) {
int res = 0;
for (; x; x -= x & -x) res += c[x];
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", fa + i);
if (!fa[i]) rt = i;
else addEdge(fa[i], i);
}
dep[rt] = 1;
dfs1(rt);
dfs2(rt, rt);
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
int opt; scanf("%d", &opt);
if (opt == 1) {
int x, y, c; scanf("%d%d%d", &x, &y, &c);
p[i] = lca(x, y);
d[i] = dep[x] + dep[y] - dep[p[i]] - dep[fa[p[i]]];
if (i - c - 1 > 0) q[i - c - 1].push_back( (Q) { x, y, p[i], i } );
} else {
scanf("%d", T + i);
}
}
for (int i = 1; i <= m; i++) {
if (T[i])
add(dfn[T[i]], 1), add(dfn[T[i]] + sz[T[i]], -1);
for (int j = 0; j < q[i].size(); j++) {
Q u = q[i][j];
ans[u.id] = ask(dfn[u.x]) + ask(dfn[u.y]) - ask(dfn[u.p]) - ask(dfn[fa[u.p]]);
}
}
for (int i = 1; i <= m; i++)
if (p[i]) printf("%d %d\n", d[i], ans[i]);
return 0;
}
[HNOI2011]括號修復 / [JSOI2011]括號序列
括號序合法的另一種判定:
- 每個字首的 \((\) 數都大於等於 \()\) 的個數
- 每個字尾的 \()\) 的個數都大於等於 \((\) 的個數。
將 \((\) 視為 \(-1\),\()\) 視為 \(1\)。
設 \(pre_i, suf_i\) 分別為前後綴和
合法條件是滿足 \(\max(pre) \le 0\) 且 \(\min(suf) \ge 0\)
答案是 \(\ \lceil \frac{max(pre)}{2} \rceil + \lceil \frac{-min(suf)}2 \rceil\)
證明
必要性:每次將右括號改成左括號會讓右邊的 \(pre +2\),因此至少要加 $ \lceil \frac{max(pre)}{2} \rceil$ 才能讓每個合法。字尾同理。
充分性:考慮構造,對於 \(pre\) 來說,把從左往右 $ \lceil \frac{max(pre)}{2} \rceil$ 個右括號改成左括號,對於 \(pre_i\) 至少 \(i\) 前面有 \(-pre_i\) 個左括號,所以改的時候一定能影響到 \(i\)。對於 \(suf\) 同理。
因為要 swap,所以只能用平衡樹維護區間和、前後綴最大值最小值。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#define ls t[p].l
#define rs t[p].r
using namespace std;
const int N = 100005;
struct Fhq{
int l, r, rand, sz, sum, val, preMin, preMax, sufMin, sufMax, rep, swap, inv;
} t[N * 2];
int n, q, rt, a, b, idx;
char s[N], opt[10], c[2];
void inline replace(int p, int v) {
t[p].rep = v, t[p].swap = t[p].inv = 0, t[p].val = v, t[p].sum = v * t[p].sz;
if (v == 1) t[p].preMin = t[p].sufMin = 0, t[p].preMax = t[p].sufMax = v * t[p].sz;
else t[p].preMin = t[p].sufMin = v * t[p].sz, t[p].preMax = t[p].sufMax = 0;
}
void inline swap(int p) {
t[p].swap ^= 1;
swap(t[p].l, t[p].r), swap(t[p].preMin, t[p].sufMin);
swap(t[p].preMax, t[p].sufMax);
}
void inline invert(int p) {
t[p].inv ^= 1, t[p].sum *= -1, t[p].val *= -1;
swap(t[p].preMin, t[p].preMax);
t[p].preMin *= -1, t[p].preMax *= -1;
swap(t[p].sufMin, t[p].sufMax);
t[p].sufMin *= -1, t[p].sufMax *= -1;
}
void inline pushdown(int p) {
if (t[p].rep) {
if (ls) replace(ls, t[p].rep);
if (rs) replace(rs, t[p].rep);
t[p].rep = 0;
}
if (t[p].swap) {
if (ls) swap(ls);
if (rs) swap(rs);
t[p].swap = 0;
}
if (t[p].inv) {
if (ls) invert(ls);
if (rs) invert(rs);
t[p].inv = 0;
}
}
void inline pushup(int p) {
t[p].sz = t[ls].sz + t[rs].sz + 1;
t[p].sum = t[ls].sum + t[rs].sum + t[p].val;
t[p].preMin = min(t[ls].preMin, t[ls].sum + t[p].val + t[rs].preMin);
t[p].preMax = max(t[ls].preMax, t[ls].sum + t[p].val + t[rs].preMax);
t[p].sufMin = min(t[rs].sufMin, t[rs].sum + t[p].val + t[ls].sufMin);
t[p].sufMax = max(t[rs].sufMax, t[rs].sum + t[p].val + t[ls].sufMax);
}
int inline addNode(int v) {
t[++idx] = (Fhq) { 0, 0, rand(), 1, v, v, min(v, 0), max(v, 0), min(v, 0), max(v, 0), 0, 0, 0 };
return idx;
}
int build(int l, int r) {
if (l > r) return 0;
if (l == r) return addNode(s[r] == '(' ? -1 : 1);
int mid = (l + r) >> 1, p = addNode(s[mid] == '(' ? -1 : 1);
t[p].l = build(l, mid - 1);
t[p].r = build(mid + 1, r);
pushup(p);
return p;
}
// val(a) < val(b)
int merge(int A, int B) {
if (!A || !B) return A + B;
if (t[A].rand < t[B].rand) {
pushdown(A);
t[A].r = merge(t[A].r, B);
pushup(A); return A;
} else {
pushdown(B);
t[B].l = merge(A, t[B].l);
pushup(B); return B;
}
}
void split(int p, int k, int &x, int &y) {
if (!p) { x = y = 0; return; }
pushdown(p);
if (t[ls].sz + 1 <= k) x = p, split(t[p].r, k - t[ls].sz - 1, t[p].r, y);
else y = p, split(t[p].l, k, x, t[p].l);
pushup(p);
}
int main() {
srand(time(0));
int X, Y, Z;
scanf("%d%d%s", &n, &q, s + 1);
rt = build(1, n);
while (q--) {
scanf("%s%d%d", opt, &a, &b);
split(rt, a - 1, X, Y);
split(Y, b - a + 1, Y, Z);
if (*opt == 'R') {
scanf("%s", c);
replace(Y, *c == '(' ? -1 : 1);
} else if (*opt == 'Q') printf("%d\n", (t[Y].preMax + 1) / 2 + (-t[Y].sufMin + 1) / 2);
else if (*opt == 'S') swap(Y);
else if (*opt == 'I') invert(Y);
rt = merge(X, merge(Y, Z));
}
return 0;
}
[CQOI2011]動態逆序對
設 \(t_i\) 表示 \(i\) 被刪除的時間,如果沒被刪除就是無窮
統計刪除 \(i\) 去掉的逆序對數:滿足 \(t_i < t_j, i > j, a_i < a_j\) 或 \(t_i < t_j, i < j, a_i > a_j\) 的 \(j\) 的個數。離線三維偏序 CDQ 就行了。
程式碼懶得寫。
[JLOI2011]不等式組
轉化成討論 \(a\) 的正負性,轉化成 \(a > k\) 或 \(a < k\) 的形式,樹狀陣列就行了。
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 100005, S = 2000005, L = 1e6 + 1;
int n, cnt, tot, c[2][S];
int pos[N], op[N];
char opt[6];
void inline add(int x, int k, int o) {
for (; x < S; x += x & -x) c[o][x] += k;
}
int inline ask(int x, int o) {
int res = 0;
for (; x; x -= x & -x) res += c[o][x];
return res;
}
int main() {
scanf("%d", &n);
while (n--) {
scanf("%s", opt);
if (opt[0] == 'A') {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
++tot;
if (a == 0) cnt += (b > c), op[tot] = -1, pos[tot] = (b > c);
else if (a > 0) {
LL x = max(-1000000, (int)(floor((double)(c - b) / a) + 1));
if (x > 1000000) op[tot] = 2;
else add(pos[tot] = x + L, 1, op[tot] = 0);
} else if (a < 0) {
LL x = min(1000000, (int)(ceil((double)(c - b) / a) - 1));
if (x < -1000000) op[tot] = 2;
else add(pos[tot] = x + L, 1, op[tot] = 1);
}
} else if (opt[0] == 'D') {
int i; scanf("%d", &i);
if (op[i] == 2) continue;
else if (op[i] == -1) cnt -= pos[i], pos[i] = 0;
else add(pos[i], -1, op[i]), op[i] = 2;
} else {
int k; scanf("%d", &k);
printf("%d\n", ask(k + L, 0) + ask(S - 1, 1) - ask(k - 1 + L, 1) + cnt);
}
}
return 0;
}
[Ynoi2010]y-fast trie
先把所有數 \(\bmod C\)。
然後分類討論:
- 若 \(x + y \ge C\),則對應數值為 \(x + y - C\)
- 若 \(x + y < C\),則對應為 \(x + y\)
第一類直接選出當前集合的最大值和次大值即可。
第二類,設 \(find(x)\) 為 \(x\) 數在當前集合中滿足 \(x + y < C\) 且 \(y\) 儘量大的 \(y\),即 \(\le C - x - 1\) 的集合中數的最大值,即 \(x\) 的最優匹配。
單項每次刪除插入要維護很久,我們發現,如果設 \(find(x) = y, find(y) = z\),如果 \(x < z\),那麼 \(x + y < z + y\),那麼前者這個數對就肯定不是最優的了,不考慮,所以我們發現最優的肯定是雙向匹配,我們不妨維護兩個集合 \(a, b\),\(a\) 是當前集合中所有的數,\(b\) 包含雙向匹配的數值。
插入:看 \(x + y\) 如果是雙向匹配加入,如果原先 \(y, z\) 是最優的就刪除 \(y + z\)。
刪除同理。集合可以用 \(\text{multiset}\)。
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
typedef multiset<int>::iterator MIT;
int n, C, last, sz;
multiset<int> a, b;
int inline find(int x, int op) {
if (x == -1) return -1;
MIT it = a.lower_bound(C - x);
if (it == a.begin()) return -1; --it;
if (op == 1 && *it == x && a.count(x) == 1) return it == a.begin() ? -1 : *--it;
else return *it;
}
void inline insert(int x) {
++sz;
int y = find(x, 0), z = find(y, 1), w = find(z, 1);
if (y != -1 && x > z) {
if (z != -1 && w == y) b.erase(b.find(y + z));
b.insert(x + y);
}
a.insert(x);
}
void inline del(int x) {
--sz, a.erase(a.find(x));
if (!sz) return;
int y = find(x, 0), z = find(y, 1), w = find(z, 1);
if (y != -1 && x > z) {
if (z != -1 && w == y) b.insert(y + z);
b.erase(b.find(x + y));
}
}
int inline query() {
int res; MIT it = a.end();
--it;
if (a.count(*it) > 1) res = (2 * (*it)) % C;
else {
res = *it; (res += *--it) %= C;
}
return max(res, b.empty() ? 0 : *--b.end());
}
int main() {
scanf("%d%d", &n, &C);
while (n--) {
int opt, x; scanf("%d%d", &opt, &x); x ^= last;
if (opt == 1) insert(x % C);
else del(x % C);
if (sz < 2) puts("EE"), last = 0;
else printf("%d\n", last = query());
}
return 0;
}
[ZJOI2013]K大數查詢
整體二分 + 支援區間加區間求和的樹狀陣列
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 50005;
int n, m, ans[N];
bool vis[N];
struct Q{
int op, l, r, id;
LL c;
} q[N], a[N], b[N];
struct BIT {
LL c[2][N];
void inline add(int x, LL k, int o) {
for (; x <= n; x += x & -x) c[o][x] += k;
}
LL inline ask(int x, int o) {
LL res = 0;
for (; x; x -= x & -x) res += c[o][x];
return res;
}
// 區間 + k;
void change(int l, int r, int k) {
add(l, k, 0), add(l, (LL)k * l, 1);
add(r + 1, -k, 0), add(r + 1, -(LL)k * (r + 1), 1);
}
// 字首和
LL query(int x) {
return ask(x, 0) * (x + 1) - ask(x, 1);
}
} t;
void solve(int L, int R, int l, int r) {
if (L == R) {
for (int i = l; i <= r; i++)
if (q[i].op == 2) ans[q[i].id] = R;
return;
}
int mid = (L + R) >> 1, lt = 0, rt = 0;
for (int i = l; i <= r; i++) {
if (q[i].op == 1) {
if (q[i].c <= mid) a[++lt] = q[i];
else b[++rt] = q[i], t.change(q[i].l, q[i].r, 1);
} else {
LL cnt = t.query(q[i].r) - t.query(q[i].l - 1);
if (cnt < q[i].c) q[i].c -= cnt, a[++lt] = q[i];
else b[++rt] = q[i];
}
}
for (int i = l; i <= r; i++)
if (q[i].op == 1 && q[i].c > mid) t.change(q[i].l, q[i].r, -1);
for (int i = 1; i <= lt; i++) q[l + i - 1] = a[i];
for (int i = 1; i <= rt; i++) q[l + lt + i - 1] = b[i];
solve(L, mid, l, l + lt - 1); solve(mid + 1, R, l + lt, r);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d%d%lld", &q[i].op, &q[i].l, &q[i].r, &q[i].c);
q[i].id = i;
if (q[i].op == 2) vis[i] = true;
}
solve(-n, n, 1, m);
for (int i = 1; i <= m; i++)
if (vis[i]) printf("%d\n", ans[i]);
return 0;
}
[HNOI2015]接水果
發現如果考慮對於一個水果能接到它的盤子,這是一條鏈的包含關係不好做,但是考慮一個盤子能接到的水果,就是子樹關係了,可以用 \(dfs\) 序表示成區間關係。
設 \(L_u = dfn_u, R_u = dfn_u + size_u - 1\),即 \([L_u, R_u]\) 是 \(u\) 子樹的 \(dfs\) 序。
先不妨假設 \(dfn_a < dfn_b, dfn_u < dfn_v\),設 \(a, b\) 的 \(\text{LCA}\) 是 \(p\)
- 若 \(p = a\),設 \(a, b\) 這條鏈 \(a\) 下面一個點是 \(x\),那麼一個點需在 \(x\) 子樹外(\([1, L_x - 1]\) 或 \([R_x + 1, n]\)),一個需在 \(b\) 子樹內(\([L_b, R_b]\)),由於我們事先定好了 \(dfn_u < dfn_v\) 故兩種情況,且一定滿足 \(L_x \le L_b\):
- \(1 \le dfn_u \le L_x - 1\) 且 \(L_b \le dfn_v \le R_b\)
- \(L_b \le dfn_u \le R_b\) 且 \(R_x + 1 \le dfn_v \le n\)
- 若 \(p \not= a\),那麼 \(a, b\) 即分屬兩棵子樹:
- \(L_a \le dfn_u \le R_a\) 且 \(L_b \le dfn_v \le R_b\)
這樣問題變成了矩陣加,單點求第 \(k\) 小。
差分 + 整體二分轉化為單點加,矩陣求和。
整體二分 + 掃描線樹狀陣列即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define rint register int
using namespace std;
const int N = 40005, S = 16;
int n, p, Q, dfn[N], sz[N], tot, ans[N], d[N], len;
int L[N], R[N], dfncnt, fa[N][S], dep[N], c[N];
int head[N], numE = 0;
struct E{
int next, v;
} e[N << 1];
void inline addEdge(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
}
struct Opt{
int x, y, z, c, id;
bool operator < (const Opt &b) const {
if (x == b.x) return id < b.id;
return x < b.x;
}
} q[9 * N], a[9 * N], b[9 * N];
void inline addQ(int x1, int x2, int y1, int y2, int c) {
q[++tot] = (Opt) { x1, y1, 1, c, 0 };
q[++tot] = (Opt) { x1, y2 + 1, -1, c, 0 };
q[++tot] = (Opt) { x2 + 1, y1, -1, c, 0 };
q[++tot] = (Opt) { x2 + 1, y2 + 1, 1, c, 0 };
}
void dfs(int u) {
sz[u] = 1, dfn[u] = ++dfncnt;
for (int i = 1; i < S && fa[u][i - 1]; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u][0]) continue;
dep[v] = dep[u] + 1, fa[v][0] = u;
dfs(v);
sz[u] += sz[v];
}
L[u] = dfn[u], R[u] = dfn[u] + sz[u] - 1;
}
int inline lca(int x, int y, int &z) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = S - 1; ~i; i--)
if (dep[x] - (1 << i) > dep[y]) x = fa[x][i];
z = x; x = fa[x][0];
if (x == y) return x;
for (int i = S - 1; ~i; i--)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
void inline add(int x, int k) {
for (; x <= n; x += x & -x) c[x] += k;
}
void inline clear(int x) {
for (; x <= n; x += x & -x) c[x] = 0;
}
int inline ask(int x) {
int res = 0;
for (; x; x -= x & -x) res += c[x];
return res;
}
void solve(int L, int R, int l, int r) {
if (l > r) return;
if (L == R) {
for (rint i = l; i <= r; i++)
if (q[i].id) ans[q[i].id] = d[R];
return;
}
int mid = (L + R) >> 1, lt = 0, rt = 0;
for (rint i = l; i <= r; i++) {
if (!q[i].id) {
if (q[i].c <= d[mid]) add(q[i].y, q[i].z), a[++lt] = q[i];
else b[++rt] = q[i];
} else {
int cnt = ask(q[i].y);
if (q[i].z <= cnt) a[++lt] = q[i];
else q[i].z -= cnt, b[++rt] = q[i];
}
}
for (rint i = l; i <= r; i++)
if (!q[i].id && q[i].c <= d[mid]) clear(q[i].y);
for (rint i = 1; i <= lt; i++) q[l + i - 1] = a[i];
for (rint i = 1; i <= rt; i++) q[l + lt + i - 1] = b[i];
solve(L, mid, l, l + lt - 1); solve(mid + 1, R, l + lt, r);
}
int main() {
scanf("%d%d%d", &n, &p, &Q);
for (int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), addEdge(u, v), addEdge(v, u);
dep[1] = 1, dfs(1);
for (int i = 1, a, b, c; i <= p; i++) {
scanf("%d%d%d", &a, &b, &c); int x;
d[++len] = c;
if (dfn[a] > dfn[b]) swap(a, b);
int p = lca(a, b, x);
if (p == a) {
addQ(1, L[x] - 1, L[b], R[b], c);
addQ(L[b], R[b], R[x] + 1, n, c);
} else addQ(L[a], R[a], L[b], R[b], c);
}
sort(d + 1, d + 1 + len);
len = unique(d + 1, d + 1 + len) - d - 1;
for (int i = 1, u, v, k; i <= Q; i++) {
scanf("%d%d%d", &u, &v, &k);
if (dfn[u] > dfn[v]) swap(u, v);
q[++tot] = (Opt) { dfn[u], dfn[v], k, -1, i };
}
sort(q + 1, q + 1 + tot); solve(1, len, 1, tot);
for (int i = 1; i <= Q; i++) printf("%d\n", ans[i]);
return 0;
}
Rmq Problem / mex
設 \(pre_i\) 表示 \(i\) 的前驅($a_i = a_{pre_i}, pre_i <i $ 的最大的 \(pre_i\) )。
查詢就是查 \([1, r]\) 中 \(pre_i < l\) 的最小的 \(i\)。
離線線段樹掃一遍就行了。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200005;
int n, m, last, a[N], ans[N];
int dat[N << 2];
struct Q{
int l, r, id;
bool operator < (const Q &b) const {
return r < b.r;
}
} q[N];
void inline pushup(int p) {
dat[p] = min(dat[p << 1], dat[p << 1 | 1]);
}
void change(int p, int l, int r, int x, int k) {
if (l == r) { dat[p] = k; return; }
int mid = (l + r) >> 1;
if (x <= mid) change(p << 1, l, mid, x, k);
else change(p << 1 | 1, mid + 1, r, x, k);
pushup(p);
}
int query(int p, int l, int r, int k) {
if (l == r) return r;
int mid = (l + r) >> 1;
if (dat[p << 1] < k) return query(p << 1, l, mid, k);
else return query(p << 1 | 1, mid + 1, r, k);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q + 1, q + 1 + m);
for (int i = 1, j = 1; i <= n; i++) {
if (a[i] <= n + 1) {
change(1, 0, n + 1, a[i], i);
}
while (j <= m && q[j].r == i) {
ans[q[j].id] = query(1, 0, n + 1, q[j].l);
++j;
}
}
for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
return 0;
}
BZOJ #4771. 七彩樹
維護兩顆線段樹 \(A, B\)。
- A 以顏色為下標,深度為值
- B 以深度為下標,數量為值
A 做線段樹合併,合併到葉子的時候把重複的在 B 裡面刪掉,查的時候在 B 裡面查,因為強制線上所以把 B 可持久化一下就行了。
程式碼懶得寫。
[Ynoi2017]由乃的OJ
維護“真值表”(每一段線段樹,從左到右/從右到左經過一遍的值),這樣拆位是 \(O(n + qk \log ^2n)\) 的,只能得 50 分。
考慮壓在一起做,用二進位制運算表示邏輯過程。
設 \(a_0, a_1, b_0, b_1\) 為需要合併的 \(a, b\) 兩個真值表,\(c_0, c_1\) 是合併後的:
- \(c_0 = (a_0 \text{ and } b_1) \text{ or } (!a_0 \text{ and } b_0)\)
- \(c_1 = (a_1 \text{ and } b_1) \text{ or } (!a_1 \text{ and } b_0)\)
這樣就可以 \(O(n + q (k + \log ^2n))\) 了,能得 100 分。
程式碼懶得寫。
[SCOI2014]方伯伯的OJ
類似 NOIP2017 列隊的做法。
開可持久化線段樹,下標的絕對下標,資訊有人數和編號。另外開個 \(\text{map}\) 記錄編號位置變過的人的位置。
4 操作直接線上段樹上二分。
這樣就行了。 \(O(m \log n)\)
程式碼懶得寫。
咕咕咕,之後再寫。。