[CTSC2018]暴力写挂 边分治+虚树
分析
题目大意:给俩树 T , T ’ T,T’ T,T’,求 d e p x + d e p y − ( d e p L c a ( x , y ) + d e p L c a ′ ( x , y ) ′ ) dep_x+dep_y-(dep_{Lca(x,y)}+dep'_{Lca'(x,y)}) depx+depy−(depLca(x,y)+depLca′(x,y)′)的最大值。
比较不好处理的是
d
e
p
L
c
a
(
x
,
y
)
dep_{Lca(x,y)}
depLca(x,y)
由
D
i
s
(
x
,
y
)
=
d
e
p
x
+
d
e
p
y
−
2
d
e
p
L
c
a
(
x
,
y
)
Dis(x,y)=dep_x+dep_y-2dep_{Lca(x,y)}
Dis(x,y)=depx+depy−2depLca(x,y)可以得到
d
e
p
L
c
a
(
x
,
y
)
=
1
2
(
d
e
p
x
+
d
e
p
y
−
D
i
s
(
x
,
y
)
)
dep_{Lca(x,y)}=\frac{1}{2}(dep_x+dep_y-Dis(x,y))
depLca(x,y)=21(depx+depy−Dis(x,y))
带进去化简一下可以得到:
2
A
n
s
=
m
a
x
(
d
e
p
x
+
d
e
p
y
+
D
i
s
(
x
,
y
)
−
d
e
p
L
c
a
′
(
x
,
y
)
′
)
2Ans=max(dep_x+dep_y+Dis(x,y)-dep'_{Lca'(x,y)})
2Ans=max(depx+depy+Dis(x,y)−depLca′(x,y)′)
事实上
d
e
p
x
+
d
e
p
y
+
D
i
s
(
x
,
y
)
dep_x+dep_y+Dis(x,y)
depx+depy+Dis(x,y)这个东西可以直接用边分治来处理,假设当前边是
(
s
t
,
e
d
)
(st,ed)
(st,ed),那么设
w
x
=
(
D
i
s
(
x
,
s
t
)
+
d
e
p
x
)
w_x=(Dis(x,st)+dep_x)
wx=(Dis(x,st)+depx),在一次分治中变成要最大化
w
x
+
w
y
−
d
e
p
L
c
a
′
(
x
,
y
)
′
w_x+w_y-dep'_{Lca'(x,y)}
wx+wy−depLca′(x,y)′,直接上虚树即可。
不过复杂度是
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)的,据说出题人卡掉了。
有一种更具技巧性的做法,充分利用了边分治的性质。
我们发现,边分治有一个特点,就是每一层分治中,一个点要么再一条边的“左边”,要么在一条边的“右边”,这也是边分治优于点分治的地方——每一层只有两颗子树。
这个时候有一种操作是,根据分治树的结构每个节点动态地开一颗二叉树(暂且称其为xxcc树)。如果在分治边的左边就把它放左边,否则把它放右边。
不难发现这棵树的结构和功能与动态开点的线段树是类似的,当然,也支持启发式合并操作。
那么我们仍旧处理出
w
x
w_x
wx,只不过这回将其维护在xxcc树的每个节点上,然后遍历另一棵树,枚举
L
c
a
′
Lca'
Lca′,那么只需要处理出子树内部的
{
w
x
+
w
y
}
\{w_x+w_y\}
{wx+wy}最大值即可,这个东西可以在启发式合并的时候统计出来。
注意处理只有一个点的情况。
复杂度同线段树合并的复杂度是一样的,均摊的
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
代码
#include<bits/stdc++.h>
const int N = 8e5 + 10, M = N << 1, B = N * 20, inf = 1e9;
const long long oo = 1e18;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
struct Edge {
int to[M], nx[M], w[M], pr[N], tp;
Edge() {tp = 1;}
void add(int u, int v, int _w) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp; w[tp] = _w;}
void adds(int u, int v, int w = 0) {add(u, v, w); add(v, u, w);}
}R, T, S;
int lst[N], rt[N], siz[N], *pos[N], ch[B][2], n, sums, mn, tot, sz, G;
long long dep[N], mx[B][2], ans, dec; bool del[N];
void Up(long long &a, long long b) {a = std::max(a, b);}
void ins(int u, int v, int w) {
++tot; T.adds(tot, v, w);
T.adds(lst[u], tot, 0); lst[u] = tot;
}
void Build(int u, int fa, long long de) {
dep[u] = de;
for(int i = R.pr[u], v; i; i = R.nx[i])
if((v = R.to[i]) != fa)
ins(u, v, R.w[i]), Build(v, u, de + R.w[i]);
}
void Dfs(int u, int fa, long long dis, bool p) {
if(u <= n) {
*pos[u] = ++sz;
mx[sz][p] = dis + dep[u];
mx[sz][p ^ 1] = -oo;
pos[u] = &ch[sz][p];
}
for(int i = T.pr[u], v; i;i = T.nx[i])
if(!del[i >> 1] && (v = T.to[i]) != fa)
Dfs(v, u, dis + T.w[i], p);
}
void Rt(int u, int fa) {
siz[u] = 1;
for(int i = T.pr[u], v; i; i = T.nx[i])
if(!del[i >> 1] && (v = T.to[i]) != fa) {
Rt(v, u), siz[u] += siz[v];
int tmp = std::max(siz[v], sums - siz[v]);
if(mn > tmp) mn = tmp, G = i;
}
}
void Div(int u, int pres) {
if(pres == 1) return ;
sums = pres; mn = inf; Rt(u, 0);
del[G >> 1] = true;
int x = T.to[G], y = T.to[G ^ 1], sy = pres - siz[x];
Dfs(x, 0, 0, 0); Dfs(y, 0, T.w[G], 1);
Div(x, siz[x]); Div(y, sy);
}
int Mg(int u, int v) {
if(!u || !v) return u | v;
Up(ans, mx[u][0] + mx[v][1] - dec);
Up(ans, mx[u][1] + mx[v][0] - dec);
mx[u][0] = std::max(mx[u][0], mx[v][0]);
mx[u][1] = std::max(mx[u][1], mx[v][1]);
ch[u][0] = Mg(ch[u][0], ch[v][0]);
ch[u][1] = Mg(ch[u][1], ch[v][1]);
return u;
}
void Work(int u, int fa, long long dis) {
Up(ans, dep[u] - dis << 1);
for(int i = S.pr[u], v; i; i = S.nx[i])
if((v = S.to[i]) != fa) {
Work(v, u, dis + S.w[i]);
dec = dis << 1; rt[u] = Mg(rt[u], rt[v]);
}
}
int main() {
n = ri();
for(int i = 1;i < n; ++i) {
int u = ri(), v = ri(), w = ri();
R.adds(u, v, w);
}
for(int i = 1;i <= n; ++i)
lst[i] = i, pos[i] = &rt[i];
tot = n; Build(1, 0, 0);
Div(1, tot);
for(int i = 1;i < n; ++i) {
int u = ri(), v = ri(), w = ri();
S.adds(u, v, w);
}
Work(1, 0, 0);
printf("%lld\n", ans >> 1);
return 0;
}