【洛谷6072】『MdOI R1』Path(树上无交点双路径问题)
- 给定一棵 \(n\) 个点的无根树,每条边有一个边权。
- 要求选出两条无交点的树上路径,使得两条路径边权异或和之和最大。
- \(2\le n\le3\times10^4\)
树上无交点双路径问题
这类问题一种可以认为是套路的解决方案是枚举一个分界点 \(x\),让这两个路径一条在 \(x\) 子树内,一条在 \(x\)子树外。
对于这道题,首先是一个经典转化,令 \(v_x\) 表示从 \(x\) 到根所有边权异或和,那么路径 \((x,y)\) 的边权异或和就是 \(v_x\oplus v_y\)。
在 \(x\) 子树内的情况可以 DSU on Tree+Trie 求解。
在 \(x\) 子树外的情况,考虑我们利用 Trie 求出任意一对异或值最大的点对 \((A,B)\),那么除了 \(A\) 到根的路径和 \(B\) 到根的路径上的点,都满足 \((A,B)\) 这条路径在子树外。而对于这两条路径,我们直接从根节点走到 \(A/B\),每次把不在子树内的点加入 Trie,可以维护出这些点子树外的最大异或值。
坑掉的单 \(\log\) 做法
实际上,更精细地实现可以做到完全一只 \(\log\)。
首先改成以 \((A,B)\) 路径上某个节点为根。
若分界点不在 \((A,B)\) 路径上,由于确定了子树外的路径,我们实际上没有必要对每个点求解子树内的答案,而是只去求解与这条路径相邻的点的子树内(它们比子树节点内包含更多路径,肯定更优),这样一来每个点就只会被计算一次。
若分界点在 \((A,B)\) 路径上,发现之前求解它子树外答案的做法本来就是一只 \(\log\) 的。而子树内的答案可以仿照这个做法,反过来从 \(A/B\) 向上,每次把子树内不在 Trie 中的点加入 Trie 即可。
但由于是在已经用两只 \(\log\) 的做法通过此题后才知道这个做法的,所以不想再写了。
代码:\(O(n\log n\log V)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 30000
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,o,ans,A,B,V,G[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}e[N<<1];map<int,int> id;
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int rt;struct Trie
{
#define New() (Et?Ep[Et--]:++Nt)
#define Cls(x) (O[x].Sz=O[x].S[0]=O[x].S[1]=0)
int Nt,Et,Ep[N*30];struct node {int Sz,S[2];}O[N*30];I void Cl() {W(Nt) Cls(Nt),--Nt;rt=Et=0;}//情况
I void A(int& rt,CI x,CI d=29) {!rt&&(rt=New()),++O[rt].Sz,~d&&(A(O[rt].S[x>>d&1],x,d-1),0);}//加元素
I void D(int& rt,CI x,CI d=29) {~d&&(D(O[rt].S[x>>d&1],x,d-1),0),!--O[rt].Sz&&(Cls(rt),Ep[++Et]=rt,rt=0);}//删元素
I int Q(CI rt,CI x,CI d=29) {if(!~d) return 0;RI t=x>>d&1;return O[rt].S[t^1]?Q(O[rt].S[t^1],x,d-1)|(1<<d):Q(O[rt].S[t],x,d-1);}//询问与x的最大异或值
}T;
int v[N+5],sz[N+5],f[N+5],g[N+5];I void dfs(CI x) {sz[x]=1;for(RI i=lnk[x],y;i;i=e[i].nxt)
(y=e[i].to)^f[x]&&(v[y]=v[f[y]=x]^e[i].v,dfs(y),sz[x]+=sz[e[i].to],sz[e[i].to]>sz[g[x]]&&(g[x]=e[i].to));}
#define Ins(v) (T.A(rt,v),o=max(o,T.Q(rt,v)))//把一个点加入Trie树,同时更新最大异或和
I void Tg(CI x) {Ins(v[x]);for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^f[x]&&(Tg(e[i].to),0);}//加入x子树内的点
I void Dl(CI x) {T.D(rt,v[x]);for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^f[x]&&(Dl(e[i].to),0);}//删除x子树内的点
I void Wk(CI x,CI t=0) {x^1&&(Wk(f[x],x),0);G[x]=o,Ins(v[x]);for(RI i=lnk[x],y;i;i=e[i].nxt) (y=e[i].to)^f[x]&&y^t&&(Tg(y),0);}//求出x到根所有点子树外的答案
I void DSU(CI x) {RI i,y;for(i=lnk[x];i;i=e[i].nxt) (y=e[i].to)^f[x]&&y^g[x]&&(DSU(y),Dl(y),o=0);//先求解轻儿子,做一个删一个
if(g[x]) for(DSU(g[x]),i=lnk[x];i;i=e[i].nxt) (y=e[i].to)^f[x]&&y^g[x]&&(Tg(y),0);Ins(v[x]),x^1&&(ans=max(ans,o+(~G[x]?G[x]:V)),0);}//求解重儿子,加上轻儿子,加上当前点,以当前点为分界点更新答案
int main()
{
RI i,x,y,z;for(read(n),i=1;i^n;++i) read(x,y,z),add(x,y,z),add(y,x,z);
for(dfs(1),V=-1,i=1;i<=n;++i) G[i]=-1,id[v[i]]=i,T.A(rt,v[i]),(x=T.Q(rt,v[i]))>V&&(A=i,B=id[v[i]^x],V=x);//任意求出一条异或值最大的路径
return T.Cl(),o=0,Wk(A),T.Cl(),o=0,Wk(B),T.Cl(),o=0,DSU(1),printf("%d\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒