洛谷 P4546 [CTSC2018] 暴力写挂 题解

前言

传送门

又是毒瘤数据结构。

题意

给定两棵树 \(T\)\(T'\),节点编号为 \(1\sim n\),以 \(1\) 号节点为根。对于所有点对 \((x, y)\),求出:

\[dep_x + dep_y -\left(dep_{\text{lca}(x,y)}+dep'_{\text{lca}'(x,y)}\right) \]

的最大值。\(dep_u\) 定义为 \(u\) 到根的路径上边权总和。

数据范围:\(n\le 4\times10^5\)

思路

题意转化

枚举所有点对的复杂度显然是爆炸的,首先考虑转化题意。

我们考虑枚举第一棵树上的 LCA。固定 \(u=\text{lca}(x, y)\),我们要求 \(x,y\)\(u\) 的不同子树中,或者一个是 \(u\),则式子可以改写成:

\[F(x, y)=dep_x+dep_y-dep_u-dep'_{\text{lca}'(x, y)} \]

我们考虑在枚举 \(u\) 的儿子时,动态地向第二棵树上插入已枚举过的点。设 \(x\) 是这个集合中的点。那么,如果我们将要枚举 \(y\),只要我们能够快速查询:

\[\max_{x\in T_2}\left(dep_x-dep'_{\text{lca}'(x, y)}\right) \]

就足以降低复杂度。

于是问题转化为:在第一棵树上枚举 LCA,统计其所有子树的贡献,并且在第二棵树上维护一种数据结构,支持:

  1. 插入一个点 \(x\),其权值设为 \(a_x = dep_x\)

  2. 对于一个点 \(y\),查询 \(\max_{x\in T_2}\left(a_x-dep'_{\text{lca}'(x, y)}\right)\)

第一棵树的维护

我们要枚举所有的 LCA,并统计不同子树对它的贡献,这可以用 dsu on tree 解决。具体来说,分为以下步骤:

  1. 我们递归处理轻子树,不保留贡献;

  2. 再递归处理重子树,保留贡献;

  3. 再次回到轻子树,遍历其中的点,并在第二棵树上查找答案,用上面的式子更新答案,然后将这些点全部插入第二棵树。

  4. 在第二棵树上查询当前点自身的贡献,并把当前点自身插入第二棵树。

  5. 上述结果临时保存在该节点处,处理完一个节点后,再更新全局最大值。

第二棵树的维护

考虑如何维护我们要查询的 \(\max_{x\in T_2}\left(a_x-dep'_{\text{lca}'(x, y)}\right)\)。不妨分类讨论:

  1. \(x\)\(y\) 的轻子树中。则 \(\text{lca}'(x,y)=y\),答案是 \(a_x-dep'_y\)

  2. \(x\)\(y\) 在同一条重链上,\(x\)\(y\) 后代。则 \(\text{lca}'(x,y)=y\),答案仍为 \(a_x-dep'_y\)

  3. \(x\)\(y\) 在同一条重链上,\(y\)\(x\) 后代。则 \(\text{lca}'(x,y)=x\),答案为 \(a_x-dep'_x\)

  4. \(x\)\(y\) 在某个点 \(p\) 的不同轻子树上。则 \(\text{lca}'(x,y)=p\),答案为 \(a_x-dep'_p\)

  5. \(x\) 在某个点 \(p\) 的重子树上,\(y\)\(p\) 轻子树上。则 \(\text{lca}'(x,y)=p\),答案为 \(a_x-dep'_p\)

那么我们考虑在第二棵树上做重链剖分。

对于操作 2 和 3,相当于是在重链上做前缀和后缀最大值查询。我们考虑给每条重链分配两个 BIT ,称作 updn,分别记录从链头向下、从链尾向上的前缀最大值。显然 up 中维护 \(a_x-dep'_x\),对应情况 3,dn 中维护 \(a_x\),对应情况 2。

对于操作 1,4,5,我们考虑对于每个有轻子树的节点,分配两个 BIT,称作 vlvr,分别记录所有轻儿子正序、逆序的前缀最大值。这就可以覆盖所有的查询。显然它们都维护 \(a_x\)。在查询时,我们先查这个点自身的 vlvr,对应情况 1;在沿重链一路向上跳时,每到达一个 LCA,都查询其 vlvr ,对应情况 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;
}
posted @ 2026-08-25 14:12  EtherealYz  阅读(4)  评论(0)    收藏  举报