【题解】P4565 [CTSC2018]暴力写挂
能写点分为什么要写这种玄学东西。
思路
边分树合并。
首先考虑点分,发现只会 T 飞的做法。但是答案的形式有点意思,换一下写法:
\(ans = \frac{1}{2} \max(\operatorname{dis}(x, y) + \operatorname{depth}(x) + \operatorname{depth}(y) + \operatorname{depth^{\prime}}(\operatorname{lca^{\prime}}(x, y)))\)
有树上距离了,好办。\(\operatorname{dis}(x, y)\) 可以看成边分时的 \(x, y\) 深度之和,于是可以把它和 \(\operatorname{depth}(x), \operatorname{depth}(y)\)分别拆分给 \(x, y\),等价于给 \(x, y\) 赋上点权 \(val\)
正常来说这里和点分一样建一棵 \(T^{\prime}\) 的虚树,然后枚举每个 \(\operatorname{lca^{\prime}}\) 的贡献就做完了。但是这样的时间复杂度是 \(O(n \log^2 n)\),被无良出题人卡掉了。
可以倒过来想,先枚举 \(\operatorname{lca^{\prime}}\),再考虑它的子树中一对被同一层边分的结点 \(x, y\) 的贡献。
考虑一对结点 \(x, y\) 第一次产生贡献的条件。当它们最后一次被一起边分之后,它们会处在两层不同的边分中。
那么根据边分树的性质,它们在边分树上的路径有一段公共前缀,然后分别走进父结点的左右子结点中。
只维护子结点构成的路径时,可以考虑遍历一遍子结点的边分树,然后和当前的答案合并。
然而我们维护的是子树,所以需要将边分树不断向上合并,可以用类似线段树合并的方法实现。
维护答案也不用上面那么麻烦,直接在合并的时候一起算就行。
时间复杂度 \(O(n \log n)\),三度化和线段树合并有一点常数。
代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 4e5 + 5;
const int nd_sz = 1e7 + 5;
const int maxe = 2e6 + 5;
const ll inf = 1e18;
int n, gpt, gb;
int rt[maxn];
int son[nd_sz][2];
ll ans = -inf, res = -inf;
ll val[nd_sz];
vector<int> g[maxn], w[maxn];
namespace t1
{
struct Edge
{
int to, nxt, w;
bool vis;
} edge[maxe];
int ecnt = 1, rte, tot, min_sz;
int head[nd_sz], rdp[nd_sz], sz[nd_sz];
ll dep[nd_sz], pre[nd_sz];
void add_edge(int u, int v, int w)
{
edge[++ecnt] = (Edge){v, head[u], w};
head[u] = ecnt;
}
void get_rdp(int u, int fa)
{
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (v == fa) continue;
rdp[v] = rdp[u] + 1;
dep[v] = dep[u] + edge[i].w;
get_rdp(v, u);
}
}
void cnt_sz(int u, int fa)
{
tot++;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (edge[i].vis || (v == fa)) continue;
// if (v == 4) printf("debug %d\n", i);
cnt_sz(v, u);
}
}
void get_sz(int u, int fa)
{
sz[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (edge[i].vis || (v == fa)) continue;
get_sz(v, u);
sz[u] += sz[v];
int tsz = abs(tot - 2 * sz[v]);
if (tsz < min_sz) min_sz = tsz, rte = i;
}
}
void get_pre(int u, int fa, int dir)
{
if (u <= n)
{
int cur = gb++;
val[cur] = dep[u] + pre[u];
// printf("son[%d][%d] = %d\n", rt[u], dir, cur);
son[rt[u]][dir] = cur;
rt[u] = cur;
}
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (edge[i].vis || (v == fa)) continue;
pre[v] = pre[u] + edge[i].w;
get_pre(v, u, dir);
}
}
void solve(int u)
{
tot = 0;
cnt_sz(u, 0);
if (tot == 1) return;
// printf("%d\n", tot);
min_sz = 1e9;
get_sz(u, 0);
int a = edge[rte].to, b = edge[rte ^ 1].to;
// printf("%d <-> %d\n", edge[4].vis, edge[4 ^ 1].vis);
// printf("solving %d, %d, %d %d : %d -> %d\n", u, rte, a, b, rte, rte);
edge[rte].vis = edge[rte ^ 1].vis = true;
pre[a] = edge[rte].w, pre[b] = 0;
get_pre(a, 0, rdp[a] > rdp[b]);
get_pre(b, 0, rdp[b] > rdp[a]);
solve(a), solve(b);
}
}
int merge(int u, int v)
{
if (!(u && v)) return u | v;
if (son[u][0] && son[v][1]) res = max(res, val[son[u][0]] + val[son[v][1]]);
if (son[v][0] && son[u][1]) res = max(res, val[son[v][0]] + val[son[u][1]]);
// printf("debug %lld\n", res);
val[u] = max(val[u], val[v]);
son[u][0] = merge(son[u][0], son[v][0]);
son[u][1] = merge(son[u][1], son[v][1]);
return u;
}
namespace t2
{
struct Edge
{
int to, nxt, w;
} edge[maxe];
int ecnt = 0;
int head[maxn];
ll dep[maxn];
void add_edge(int u, int v, int w)
{
edge[++ecnt] = (Edge){v, head[u], w};
head[u] = ecnt;
}
void dfs(int u, int fa)
{
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (v == fa) continue;
dep[v] = dep[u] + edge[i].w;
dfs(v, u);
// printf("%d -> %d\n", u, dep[v]);
res = -inf;
rt[u] = merge(rt[u], rt[v]);
// printf("%lld\n", res);
if (res != -inf) ans = max(ans, res - 2 * dep[u]);
}
}
}
void add_edge(int u, int v, int d) { g[u].push_back(v); w[u].push_back(d); }
void rebuild(int u, int fa)
{
int lst = u;
for (int i = 0; i < g[u].size(); i++)
{
int v = g[u][i], d = w[u][i];
if (v == fa) continue;
int cur = gpt++;
t1::add_edge(lst, cur, 0);
t1::add_edge(cur, lst, 0);
t1::add_edge(cur, v, d);
t1::add_edge(v, cur, d);
rebuild(v, u);
lst = cur;
}
}
int main()
{
scanf("%d", &n);
gpt = n + 1, gb = n + 1;
for (int i = 1; i <= n; i++) rt[i] = i;
val[0] = -inf;
for (int i = 1, u, v, d; i <= n - 1; i++)
{
scanf("%d%d%d", &u, &v, &d);
add_edge(u, v, d);
add_edge(v, u, d);
}
for (int i = 1, u, v, d; i <= n - 1; i++)
{
scanf("%d%d%d", &u, &v, &d);
t2::add_edge(u, v, d);
t2::add_edge(v, u, d);
}
rebuild(1, 0);
t1::get_rdp(1, 0);
t1::solve(1);
// puts("done");
for (int i = 1; i <= n; i++) rt[i] = i;
t2::dfs(1, 0);
ans /= 2;
for (int i = 1; i <= n; i++) ans = max(ans, t1::dep[i] - t2::dep[i]);
printf("%lld\n", ans);
return 0;
}
/*
6
1 2 2
1 3 0
2 4 1
2 5 -7
3 6 0
1 2 -1
2 3 -1
2 5 3
2 6 -2
3 4 8
5
*/