洛谷 P4546 [CTSC2018] 暴力写挂 题解
前言
传送门。
又是毒瘤数据结构。
题意
给定两棵树 \(T\) 和 \(T'\),节点编号为 \(1\sim n\),以 \(1\) 号节点为根。对于所有点对 \((x, y)\),求出:
的最大值。\(dep_u\) 定义为 \(u\) 到根的路径上边权总和。
数据范围:\(n\le 4\times10^5\)。
思路
题意转化
枚举所有点对的复杂度显然是爆炸的,首先考虑转化题意。
我们考虑枚举第一棵树上的 LCA。固定 \(u=\text{lca}(x, y)\),我们要求 \(x,y\) 在 \(u\) 的不同子树中,或者一个是 \(u\),则式子可以改写成:
我们考虑在枚举 \(u\) 的儿子时,动态地向第二棵树上插入已枚举过的点。设 \(x\) 是这个集合中的点。那么,如果我们将要枚举 \(y\),只要我们能够快速查询:
就足以降低复杂度。
于是问题转化为:在第一棵树上枚举 LCA,统计其所有子树的贡献,并且在第二棵树上维护一种数据结构,支持:
-
插入一个点 \(x\),其权值设为 \(a_x = dep_x\);
-
对于一个点 \(y\),查询 \(\max_{x\in T_2}\left(a_x-dep'_{\text{lca}'(x, y)}\right)\)。
第一棵树的维护
我们要枚举所有的 LCA,并统计不同子树对它的贡献,这可以用 dsu on tree 解决。具体来说,分为以下步骤:
-
我们递归处理轻子树,不保留贡献;
-
再递归处理重子树,保留贡献;
-
再次回到轻子树,遍历其中的点,并在第二棵树上查找答案,用上面的式子更新答案,然后将这些点全部插入第二棵树。
-
在第二棵树上查询当前点自身的贡献,并把当前点自身插入第二棵树。
-
上述结果临时保存在该节点处,处理完一个节点后,再更新全局最大值。
第二棵树的维护
考虑如何维护我们要查询的 \(\max_{x\in T_2}\left(a_x-dep'_{\text{lca}'(x, y)}\right)\)。不妨分类讨论:
-
\(x\) 在 \(y\) 的轻子树中。则 \(\text{lca}'(x,y)=y\),答案是 \(a_x-dep'_y\)。
-
\(x\) 与 \(y\) 在同一条重链上,\(x\) 是 \(y\) 后代。则 \(\text{lca}'(x,y)=y\),答案仍为 \(a_x-dep'_y\)。
-
\(x\) 与 \(y\) 在同一条重链上,\(y\) 是 \(x\) 后代。则 \(\text{lca}'(x,y)=x\),答案为 \(a_x-dep'_x\)。
-
\(x\) 与 \(y\) 在某个点 \(p\) 的不同轻子树上。则 \(\text{lca}'(x,y)=p\),答案为 \(a_x-dep'_p\)。
-
\(x\) 在某个点 \(p\) 的重子树上,\(y\) 在 \(p\) 轻子树上。则 \(\text{lca}'(x,y)=p\),答案为 \(a_x-dep'_p\)。
那么我们考虑在第二棵树上做重链剖分。
对于操作 2 和 3,相当于是在重链上做前缀和后缀最大值查询。我们考虑给每条重链分配两个 BIT ,称作 up 和 dn,分别记录从链头向下、从链尾向上的前缀最大值。显然 up 中维护 \(a_x-dep'_x\),对应情况 3,dn 中维护 \(a_x\),对应情况 2。
对于操作 1,4,5,我们考虑对于每个有轻子树的节点,分配两个 BIT,称作 vl 和 vr,分别记录所有轻儿子正序、逆序的前缀最大值。这就可以覆盖所有的查询。显然它们都维护 \(a_x\)。在查询时,我们先查这个点自身的 vl 和 vr,对应情况 1;在沿重链一路向上跳时,每到达一个 LCA,都查询其 vl 与 vr ,对应情况 4;查询其 dn,对应情况 5。
修改操作与查询同理,注意维护对象即可,不再赘述。
我们还需要实现清空,以满足 dsu on tree 的需求。虽然 BIT 本身不能删除,但是我们发现 dsu 每次只会处理一棵子树,所以用完后直接给受影响的位置重置为 \(-\infty\) 即可。
最终答案处理
完成 dsu on tree 后,我们还需要考虑 \((x,x)\) 这样的点对的贡献。单独计算 \(dep_x-dep'_x\) 即可。
复杂度分析
dsu on tree、重链剖分、BIT 各提供一个 \(\log\),总时间复杂度 \(O(n\log^3 n)\)。但是三者都是小常数,实际跑得飞快。
空间复杂度是 \(O(n)\) 的。注意不能给每个 BIT 都分配固定空间,那样空间会爆炸。可以考虑设置全局节点池,用多少分多少。详见代码。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define MIKU 0
using namespace std;
//快读快写。
#define SUBMIT
namespace Hatsune {
#ifdef SUBMIT
#define SIZE (1<<22)
char in[SIZE], out[SIZE], *p1 = in, *p2 = in, *p3 = out;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout),p3=out)
#define putchar(ch) (p3==out+SIZE&&flush(),*p3++=(ch))
class Flush { public:~Flush() { flush(); } }_;
#endif
namespace Miku {
template<typename type>
inline void read(type& x) {
x = 0; bool flag(0); char ch = getchar();
while(ch < '0' || ch>'9') flag ^= ch == '-', ch = getchar();
while(ch >= '0' && ch <= '9') x = (x<<1)+(x<<3)+(ch^48), ch = getchar();
flag ? x = -x : 0;
}
template<typename type, typename ...T>
inline void read(type &x, T&...y) { read(x); read(y...); }
/*- read & write -*/
template<typename type>
inline void write(type x, bool flag) {
x < 0 ? x = -x, putchar('-') : 0; short Stack[50], top(0);
do Stack[++top] = x % 10, x /= 10; while (x);
while(top) putchar(Stack[top--] | 48);
flag ? putchar('\n') : putchar(' ');
}
}
#ifdef SUBMIT
#undef SIZE
#undef getchar
#undef putchar
#undef flush
#endif
}using Hatsune::Miku::read; using Hatsune::Miku::write;
const int N = 400005;
const ll inf = 1e18;
int n; ll ans;
//树状数组维护前缀最大值。
struct BIT {
#define lowbit(x) ((x) & -(x))
ll *c; int siz;
void init(ll *t, int sz) { c = t, siz = sz; for(int i=1; i<=siz; i++) c[i] = -inf; }
void modify(int x, ll v) { for(; x<=siz; x+=lowbit(x)) c[x] = max(c[x], v); }
ll query(int x, ll res=-inf) { for(; x; x-=lowbit(x)) res = max(res, c[x]); return res;}
void clear(int x) { for(; x<=siz; x+=lowbit(x)) c[x] = -inf; }
#undef lowbit
};
//第二棵树。
struct Tree2 {
struct Edge { int v, nxt; ll w; } T[N<<1];
int h[N], tot;
void addE(int u, int v, ll w) { T[tot] = {v, h[u], w}; h[u] = tot ++; }
int siz[N], son[N], fth[N], top[N], vis[N], len[N], lsiz[N], id[N], idxc;
ll a[N], dep[N];
int crk[N], rcrk[N], vrk[N], rvrk[N]; //正反分配编号,便于 BIT 查询。
BIT up[N], dn[N], vl[N], vr[N];
ll up_[N<<1], dn_[N<<1], vl_[N<<1], vr_[N<<1]; //给 BIT 的节点池。
ll *pup = up_, *pdn = dn_, *pvl = vl_, *pvr = vr_; //用指针分配节点。
void dfs1(int u, int fa) { //重链剖分
siz[u] = 1, fth[u] = fa;
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v, w = T[e].w;
if(v == fa) continue;
dep[v] = dep[u] + w;
dfs1(v, u);
siz[u] += siz[v];
if(!son[u] || siz[son[u]]<siz[v]) son[u] = v;
}
}
void dfs2(int u, int tp) { //分配 BIT。
top[u] = tp;
if(!son[u]) {
id[tp] = ++idxc; int idx = 0;
while(top[u] == tp) rcrk[u] = ++idx, u = fth[u];
len[idxc] = idx;
up[idxc].init(pup, idx); pup += idx + 1;
dn[idxc].init(pdn, idx); pdn += idx + 1;
} else {
dfs2(son[u], tp); int idx = 0;
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v;
if(v == fth[u] || v == son[u]) continue;
vrk[v] = ++idx;
dfs2(v, v);
}
lsiz[u] = idx;
vl[u].init(pvl, idx); pvl += idx + 1;
vr[u].init(pvr, idx); pvr += idx + 1;
}
}
void update(int x) { //插入节点
ll ax = a[x]; vis[x] = 1;
while(x) {
int tp = top[x];
up[id[tp]].modify(crk[x], ax-dep[x]);
dn[id[tp]].modify(rcrk[x], ax);
x = fth[tp];
if(x) {
vl[x].modify(vrk[tp], ax);
vr[x].modify(rvrk[tp], ax);
}
}
}
ll query(int x) { //询问。
ll res = -inf;
if(lsiz[x]) {
res = max(res, vl[x].query(lsiz[x]) - dep[x]);
res = max(res, vr[x].query(lsiz[x]) - dep[x]);
}
while(x) {
int tp = top[x];
res = max(res, dn[id[tp]].query(rcrk[x]-1) - dep[x]);
res = max(res, up[id[tp]].query(crk[x]-1));
x = fth[tp];
if(vis[x]) res = max(res, a[x] - dep[x]);
if(x) {
res = max(res, vl[x].query(vrk[tp]-1) - dep[x]);
res = max(res, vr[x].query(rvrk[tp]-1) - dep[x]);
}
}
return res;
}
void clear(int x) { //清空。
vis[x] = 0;
while(x) {
int tp = top[x];
up[id[tp]].clear(crk[x]);
dn[id[tp]].clear(rcrk[x]);
x = fth[tp];
if(x) {
vl[x].clear(vrk[tp]);
vr[x].clear(rvrk[tp]);
}
}
}
void build() { //建树。
memset(h, -1, sizeof(h));
for(int i=1; i<n; i++) {
int x, y, v; read(x, y, v);
addE(x, y, v), addE(y, x, v);
}
dfs1(1, 0); dfs2(1, 1);
for(int i=1; i<=n; i++) {
crk[i] = len[id[top[i]]] - rcrk[i] + 1; //计算反向编号。
if(vrk[i]) rvrk[i] = lsiz[fth[i]] - vrk[i] + 1;
}
}
} T2;
//第一棵树。
struct Tree1 {
struct Edge { int v, nxt; ll w; } T[N<<1];
int h[N], tot;
void addE(int u, int v, ll w) { T[tot] = {v, h[u], w}; h[u] = tot ++; }
int siz[N], son[N], tmp[N], cnt;
ll dep[N], res;
void dfs(int u, int fa) { //重链剖分预处理。
siz[u] = 1;
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v, w = T[e].w;
if(v == fa) continue;
dep[v] = dep[u] + w;
dfs(v, u);
siz[u] += siz[v];
if(!son[u] || siz[son[u]]<siz[v]) son[u] = v;
}
}
void calc(int u, int fa) { //计算子树贡献。
res = max(res, T2.query(u) + dep[u]);
tmp[++cnt] = u;
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v;
if(v != fa) calc(v, u);
}
}
void clear(int u, int fa) { //清空子树贡献。
T2.clear(u);
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v;
if(v != fa) clear(v, u);
}
}
void dsu(int u, int fa, bool keep) { //dsu on tree。
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v;
if(v != fa && v != son[u]) dsu(v, u, 0);
}
if(son[u]) dsu(son[u], u, 1);
res = -inf;
for(int e=h[u]; ~e; e=T[e].nxt) {
int v = T[e].v;
if(v == fa || v == son[u]) continue;
cnt = 0; calc(v, u);
for(int i=1; i<=cnt; i++) T2.update(tmp[i]);
}
res = max(res, T2.query(u) + dep[u]);
T2.update(u);
ans = max(ans, res - dep[u]);
if(!keep) clear(u, fa);
}
void build() { //建树。
memset(h, -1, sizeof(h));
for(int i=1; i<n; i++) {
int x, y, v; read(x, y, v);
addE(x, y, v), addE(y, x, v);
}
dfs(1, 0);
for(int i=1; i<=n; i++) T2.a[i] = dep[i];
}
} T1;
int main() {
read(n);
T1.build(); T2.build();
T1.dsu(1, 0, 0);
for(int i=1; i<=n; i++) ans = max(ans, T1.dep[i]-T2.dep[i]); //单点贡献。
write(ans, 1);
return MIKU;
}

浙公网安备 33010602011771号