习题
方差
拆式子,维护支持求区间平方和和区间和的线段树,记一个加法懒标记。
#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
", 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
", ((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
", 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
", 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
", 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} ceil + lceil frac{-min(suf)}2 ceil)
证明
必要性:每次将右括号改成左括号会让右边的 (pre +2),因此至少要加 $ lceil frac{max(pre)}{2} ceil$ 才能让每个合法。后缀同理。
充分性:考虑构造,对于 (pre) 来说,把从左往右 $ lceil frac{max(pre)}{2} ceil$ 个右括号改成左括号,对于 (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
", (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
", ask(k + L, 0) + ask(S - 1, 1) - ask(k - 1 + L, 1) + cnt);
}
}
return 0;
}
[Ynoi2010]y-fast trie
先把所有数 (mod 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)。
删除同理。集合可以用 ( ext{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
", 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
", 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) 的 ( ext{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
ot= 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
", 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
", 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 ext{ and } b_1) ext{ or } (!a_0 ext{ and } b_0))
- (c_1 = (a_1 ext{ and } b_1) ext{ or } (!a_1 ext{ and } b_0))
这样就可以 (O(n + q (k + log ^2n))) 了,能得 100 分。
代码懒得写。
[SCOI2014]方伯伯的OJ
类似 NOIP2017 列队的做法。
开可持久化线段树,下标的绝对下标,信息有人数和编号。另外开个 ( ext{map}) 记录编号位置变过的人的位置。
4 操作直接在线段树上二分。
这样就行了。 (O(m log n))
代码懒得写。
咕咕咕,之后再写。。