路径上若干条树的包含

题意

别人今天期中考,而你依然在机房里为今年的 NOIP 努力刷题。

一道两道三四道,紫题黑题不会题。
暴力枚举TLE,数组开小爆零寄……

已过立冬,窗外寒风瑟瑟,但是你看到有一棵树屹立不倒,你也想成为像大树一般挺拔的人。

一、二、三……你仔细数着,发现树上有 n 个结点。

忽然你手里多出了一个东西,鼓鼓的,原来是一个大小为 m 的集合。集合里有什么呢?你思索着,打开了集合。原来集合里鼓鼓囊囊装着 m 条树上的路径。这真是一棵神奇的树呢。你瞅了瞅集合外包装,说明上表明这是由 yzh 公司研发的可重集合,真是个新鲜玩意呢。

“你好啊,OIer!”那棵树朝你说道,“我们来玩个游戏吧。”你当然答应下来。

“游戏有 q 轮。”竟然是 q 轮,而不是 qe! 轮或者 eiqπ 轮,真是有趣呢,你想道。

“每次我可以向你手上的集合里塞入新的一条路径……”这就是它的魔力吗?

“或者我给你一条路径,你要告诉我它完全包含了集合中多少条路径,怎么样?”你爽快答应下来了,真是一个好玩的游戏呢。思索片刻,你发现这竟然和你最近做的题目有些相像……


形式化题意:

一棵 n 个结点的树,你需要维护路径可重集 S,初始有 m 条路径。有 q 次操作:

  1. 查询路径 (u,v) 完全包含 S 中多少条路径;
  2. S 中插入一条路径;
  3. S 中删除一条路径。

n,m,q105,保证路径不是一个点,没有第三种操作。事实上,正解可以处理每条路径具有不同权值,删除即为贡献 1

题目分析

考虑 p=(u,v) 完全包含 p=(u,v) 的充要条件。不妨跑出每个节点的 dfn:Lu,Ru,并且令 LuLvLuLv。分类讨论一下 p 的形态:

  1. lca(u,v){u,v},即 p 为折链。
    发现 u 需要在 u 的子树里,vv 的子树里。可以用 dfn 判断子树包含关系,即 usubtree(v)Lu[Lv,Rv]
  2. lca(u,v)=u{u,v},即 p 为直链。
    那么需要 p 的其中一端在 v 的子树里。对于另一端的限制,我们首先找到 u 的一个孩子 w,满足 vsubtree(w),那么另一端需要在除了 subtree(w) 的结点中。我们可以画图帮助理解:
    DFN 序列
    p 的一端在蓝色部分中,另一端在绿色部分中。我们钦定了 LuLv,所以这里可以分成不重复的两部分:Lu[1,Lw)Lv[Lv,Rv]Lu[Lv,Rv]Lv(Rw,n]

于是,我们发现一条 S 中的路径对之后查询产生的贡献,可以表示为 Lu 上一段区间和 Lv 上一段区间。将二元组放到二维笛卡尔坐标系上,将问题转化为了如下形式:

动态往平面里插入带权矩形,查询包含某一个点的矩形权值和。

对于静态问题,直接扫描线。此题使用树套树,或者 CDQ 分治。可不要小瞧树套树!要是强制在线,只能老老实实做数据结构喽。

代码

树套树
#pragma GCC optimize("Ofast", "inline", "omit-frame-pointer", "no-stack-protector", "fast-math", "unroll-loops")
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
char ST;
const int MAX = 1 << 26;
char buf[MAX], *ip = buf, obuf[MAX], *op = obuf;
#define putchar(x) *::op++ = x
template <typename T>
inline void read(T &x) {
x = 0; char ch = *ip++;
for (; ch < 48; ch = *ip++);
for (; ch >= 48; ch = *ip++) x = (x << 3) + (x << 1) + (ch ^ 48);
}
template <typename T>
inline void write(T x) {
static short stack[20], top(0);
do stack[++top] = x % 10; while (x /= 10);
while (top) putchar(stack[top--] | 48);
}
const int N = 100005;
const int lgN = __lg(N) + 1;
int n, m, q;
vector<int> edge[N];
int dpt[N], fa[N], st[lgN][N];
int L[N], R[N], timer;
void dfs(int u) {
st[0][L[u] = ++timer] = u;
for (int v : edge[u]) if (v != fa[u]) dpt[v] = dpt[u] + 1, fa[v] = u, dfs(v);
R[u] = timer;
}
inline int Min(int u, int v) { return dpt[u] < dpt[v] ? u : v; }
// when dpt[u] == dpt[v], returns v
inline int plca(int u, int v) {
// requires L[u] < L[v], u != v
int p = __lg((v = L[v]) - (u = L[u])++);
return Min(st[p][u], st[p][v - (1 << p) + 1]);
}
struct treeNode {
int ls, rs, tag; // tag always
} tree[N * 210];
int nodeCnt, root[N];
void _add(int &idx, int trl, int trr, int l, int r) {
if (!idx) idx = ++nodeCnt;
if (l <= trl && trr <= r) return ++tree[idx].tag, void();
int mid = (trl + trr) >> 1;
if (l <= mid) _add(tree[idx].ls, trl, mid, l, r);
if (r > mid) _add(tree[idx].rs, mid + 1, trr, l, r);
}
void _sub(int &idx, int trl, int trr, int l, int r) {
if (!idx) idx = ++nodeCnt;
if (l <= trl && trr <= r) return --tree[idx].tag, void();
int mid = (trl + trr) >> 1;
if (l <= mid) _sub(tree[idx].ls, trl, mid, l, r);
if (r > mid) _sub(tree[idx].rs, mid + 1, trr, l, r);
}
int _query(int idx, int trl, int trr, int p) {
if (trl == trr) return tree[idx].tag;
int mid = (trl + trr) >> 1;
if (p <= mid)
return tree[idx].tag + (tree[idx].ls ? _query(tree[idx].ls, trl, mid, p) : 0);
return tree[idx].tag + (tree[idx].rs ? _query(tree[idx].rs, mid + 1, trr, p) : 0);
}
inline void _add(int p, int l, int r) { _add(root[p], 1, n, l, r); }
inline void _sub(int p, int l, int r) { _sub(root[p], 1, n, l, r); }
inline int _query(int p, int x) { return _query(root[p], 1, n, x); }
inline void add(int p, int l, int r) { for (; p <= n; p += p & -p) _add(p, l, r); }
inline void sub(int p, int l, int r) { for (; p <= n; p += p & -p) _sub(p, l, r); }
inline void add(int L, int R, int l, int r) { add(L, l, r), sub(R + 1, l, r); }
inline void sub(int L, int R, int l, int r) { sub(L, l, r), add(R + 1, l, r); }
inline int query(int p, int x) {
int res = 0;
for (; p; p -= p & -p) res += _query(p, x);
return res;
}
inline void modify(int Lu, int Ru, int Lv, int Rv) {
if (Lu > Ru || Lv > Rv) return;
add(Lv, Rv, Lu, Ru);
}
inline void modify(int u, int v) {
if (L[u] > L[v]) u ^= v ^= u ^= v;
int fp = plca(u, v), p = fa[fp];
if (u == p) {
// straight path
modify(1, L[fp] - 1, L[v], R[v]);
modify(L[v], R[v], R[fp] + 1, n);
} else {
// common path
modify(L[u], R[u], L[v], R[v]);
}
}
inline int pathQuery(int u, int v) {
if (L[u] > L[v]) u ^= v ^= u ^= v;
return query(L[v], L[u]);
}
char ED;
signed main() {
#ifdef XuYueming
fprintf(stderr, "Memory: %.2lf MB\n", (&ED - &ST) / 1024.0 / 1024.0);
#endif
#ifndef XuYueming
freopen("revolt.in", "r", stdin);
freopen("revolt.out", "w", stdout);
#endif
fread(buf, 1, MAX, stdin), read(n), read(m), read(q);
for (int i = 1, u, v; i < n; ++i) {
read(u), read(v);
edge[u].emplace_back(v);
edge[v].emplace_back(u);
}
dfs(1);
for (int k = 1; k < lgN; ++k)
for (int i = 1; i + (1 << k) - 1 <= n; ++i)
st[k][i] = Min(st[k - 1][i], st[k - 1][i + (1 << (k - 1))]);
for (int u, v; m--; ) read(u), read(v), modify(u, v);
for (int op, u, v; q--; ) {
read(op), read(u), read(v);
if (op == 1) write(pathQuery(u, v)), putchar('\n');
else modify(u, v);
}
fwrite(obuf, 1, op - obuf, stdout);
return 0;
}
CDQ 分治
#pragma GCC optimize("Ofast", "inline", "omit-frame-pointer", "no-stack-protector", "fast-math", "unroll-loops")
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAX = 1 << 26;
char buf[MAX], *ip = buf, obuf[MAX], *op = obuf;
#define putchar(x) *op++ = x
template <typename T>
inline void read(T &x) {
x = 0; char ch = *ip++;
for (; ch < 48; ch = *ip++);
for (; ch >= 48; ch = *ip++) x = (x << 3) + (x << 1) + (ch ^ 48);
}
template <typename T>
inline void write(T x) {
static short stack[20], top(0);
do stack[++top] = x % 10; while (x /= 10);
while (top) putchar(stack[top--] | 48);
}
const int N = 100005;
const int lgN = __lg(N) + 1;
int n, m, q;
vector<int> edge[N];
int dpt[N], fa[N], st[lgN][N];
int L[N], R[N], timer;
void dfs(int u) {
st[0][L[u] = ++timer] = u;
for (int v : edge[u]) if (v != fa[u]) dpt[v] = dpt[u] + 1, fa[v] = u, dfs(v);
R[u] = timer;
}
inline int Min(int u, int v) { return dpt[u] < dpt[v] ? u : v; }
// when dpt[u] == dpt[v], returns v
inline int plca(int u, int v) {
// requires L[u] < L[v], u != v
int p = __lg((v = L[v]) - (u = L[u])++);
return Min(st[p][u], st[p][v - (1 << p) + 1]);
}
struct Line {
int y, lx, rx, v;
} line[N << 3];
int lineCnt, qryCnt, ans[N];
inline void insert(int Lu, int Ru, int Lv, int Rv) {
if (Lu > Ru || Lv > Rv) return;
line[++lineCnt] = { Lv, Lu, Ru, 1 };
line[++lineCnt] = { Rv + 1, Lu, Ru, -1 };
}
inline void pathInsert(int u, int v) {
if (L[u] > L[v]) u ^= v ^= u ^= v;
int fp = plca(u, v), p = fa[fp];
if (u == p) {
// straight path
insert(1, L[fp] - 1, L[v], R[v]);
insert(L[v], R[v], R[fp] + 1, n);
} else {
// common path
insert(L[u], R[u], L[v], R[v]);
}
}
inline void qryInsert(int idx, int u, int v) {
if (L[u] > L[v]) u ^= v ^= u ^= v;
line[++lineCnt] = { L[v], L[u], idx, 0 };
}
int tree[N];
inline void modify(int p, int v) { for (; p <= n; p += p & -p) tree[p] += v; }
inline int query(int p) {
int res = 0;
for (; p; p &= p - 1) res += tree[p];
return res;
}
inline void modify(int l, int r, int v) { modify(l, v), modify(r + 1, -v); }
void solve(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
solve(l, mid), solve(mid + 1, r);
int p = l - 1;
for (int q = mid + 1; q <= r; ++q) {
while (p + 1 <= mid && line[p + 1].y <= line[q].y)
if (line[++p].v) modify(line[p].lx, line[p].rx, line[p].v);
if (line[q].v == 0) ans[line[q].rx] += query(line[q].lx);
}
for (; p >= l; --p) if (line[p].v) modify(line[p].lx, line[p].rx, -line[p].v);
inplace_merge(line + l, line + mid + 1, line + r + 1,
[] (const Line& a, const Line& b) -> bool {
return a.y < b.y;
}
);
}
// divide solved time order
// inplace_merge solved Y order
// data structure solved X order
signed main() {
#ifndef XuYueming
freopen("revolt.in", "r", stdin);
freopen("revolt.out", "w", stdout);
#endif
fread(buf, 1, MAX, stdin), read(n), read(m), read(q);
for (int i = 1, u, v; i < n; ++i) {
read(u), read(v);
edge[u].emplace_back(v);
edge[v].emplace_back(u);
}
dfs(1);
for (int k = 1; k < lgN; ++k)
for (int i = 1; i + (1 << k) - 1 <= n; ++i)
st[k][i] = Min(st[k - 1][i], st[k - 1][i + (1 << (k - 1))]);
for (int u, v; m--; ) read(u), read(v), pathInsert(u, v);
for (int op, u, v; q--; ) {
read(op), read(u), read(v);
if (op == 1) qryInsert(++qryCnt, u, v);
else pathInsert(u, v);
}
solve(1, lineCnt);
for (int i = 1; i <= qryCnt; ++i) write(ans[i]), putchar('\n');
fwrite(obuf, 1, op - obuf, stdout);
return 0;
}

后记

快去 A 了这道题吧,祝你成功!

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAX = 1 << 26;
char buf[MAX], *ip = buf, obuf[MAX], *op = obuf;
#define putchar(x) *::op++ = x
template <typename T>
inline void read(T &x) {
x = 0; char ch = *ip++;
for (; ch < 48; ch = *ip++);
for (; ch >= 48; ch = *ip++) x = (x << 3) + (x << 1) + (ch ^ 48);
}
template <typename T>
inline void write(T x) {
static short stack[20], top(0);
do stack[++top] = x % 10; while (x /= 10);
while (top) putchar(stack[top--] | 48);
}
const int N = 50010;
const int lgN = __lg(N) + 1;
int n, m, q;
vector<int> edge[N];
int dpt[N], fa[N], st[lgN][N];
int L[N], R[N], timer;
void dfs(int u) {
st[0][L[u] = ++timer] = u;
for (int v : edge[u]) if (v != fa[u]) dpt[v] = dpt[u] + 1, fa[v] = u, dfs(v);
R[u] = timer;
}
inline int Min(int u, int v) { return dpt[u] < dpt[v] ? u : v; }
// when dpt[u] == dpt[v], returns v
inline int plca(int u, int v) {
// requires L[u] < L[v], u != v
int p = __lg((v = L[v]) - (u = L[u])++);
return Min(st[p][u], st[p][v - (1 << p) + 1]);
}
namespace TREE {
int tree[N], tag[N], timer = -1;
inline void clear() { ++timer; }
inline void modify(int p, int f) {
for (; p <= n; p += p & -p)
if (tag[p] != timer) tag[p] = timer, tree[p] = f;
else tree[p] += f;
}
inline void modify(int l, int r, int v) {
modify(l, v), modify(r + 1, -v);
}
inline int query(int p) {
int res = 0;
for (; p; p &= p - 1)
if (tag[p] == timer) res += tree[p];
return res;
}
}
struct Line {
int y, l, r, f, idx;
} line[N * 5], tq[N * 5];
int lcnt;
int VVV[N], UUU;
int ans[N];
inline void modify(int Lu, int Ru, int Lv, int Rv, int w) {
if (Lu > Ru || Lv > Rv) return;
line[++lcnt] = { Lv, Lu, Ru, 1, -w };
line[++lcnt] = { Rv + 1, Lu, Ru, -1, -w };
}
void solve(int l, int r, int ql, int qr) {
if (ql > qr) return;
if (l == r) {
for (int i = ql; i <= qr; ++i)
if (line[i].idx > 0)
ans[line[i].idx] = VVV[l];
return;
}
int mid = (l + r) >> 1;
int L = ql - 1, R = qr + 1;
TREE::clear();
for (int i = ql; i <= qr; ++i) {
if (line[i].idx > 0) {
int v = TREE::query(line[i].l);
if (v >= line[i].f)
tq[++L] = move(line[i]);
else
line[i].f -= v, tq[--R] = move(line[i]);
} else {
if (-line[i].idx <= VVV[mid])
TREE::modify(line[i].l, line[i].r, line[i].f), tq[++L] = move(line[i]);
else
tq[--R] = move(line[i]);
}
}
reverse(tq + R, tq + qr + 1);
for (int i = ql; i <= qr; ++i) line[i] = move(tq[i]);
solve(mid + 1, r, R, qr);
solve(l, mid, ql, L);
}
signed main() {
fread(buf, 1, MAX, stdin), read(n), read(m), read(q);
for (int i = 1, u, v; i < n; ++i) {
read(u), read(v);
edge[u].emplace_back(v);
edge[v].emplace_back(u);
}
dfs(1);
for (int k = 1; k < lgN; ++k)
for (int i = 1; i + (1 << k) - 1 <= n; ++i)
st[k][i] = Min(st[k - 1][i], st[k - 1][i + (1 << (k - 1))]);
for (int i = 1, u, v, w; i <= m; ++i) {
read(u), read(v), read(w);
if (L[u] > L[v]) u ^= v ^= u ^= v;
int fp = plca(u, v), p = fa[fp];
if (u == p) {
// straight path
modify(1, L[fp] - 1, L[v], R[v], w);
modify(L[v], R[v], R[fp] + 1, n, w);
} else {
// common path
modify(L[u], R[u], L[v], R[v], w);
}
VVV[++UUU] = w;
}
for (int i = 1, u, v, k; i <= q; ++i) {
read(u), read(v), read(k);
line[++lcnt] = { L[v], L[u], 0, k, i };
ans[i] = -1;
}
sort(line + 1, line + lcnt + 1,
[] (const Line& a, const Line& b) -> bool {
if (a.y == b.y) return a.idx < b.idx; // modify first
return a.y < b.y;
}
);
sort(VVV + 1, VVV + UUU + 1);
UUU = unique(VVV + 1, VVV + UUU + 1) - VVV - 1;
solve(1, UUU, 1, lcnt);
for (int i = 1; i <= q; ++i)
write(ans[i]), putchar('\n');
fwrite(obuf, 1, op - obuf, stdout);
return 0;
}
posted @   XuYueming  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示