【题解】P6199 [EER1]河童重工
东方吧有个老哥上网搜河童重工结果出来这个题,炸出来一车 OIer,笑死我了 /kx
点分治太玄学了吧,正确写法到底是什么???
题意
给定两棵树 \(T_1, T_2\),结点一一对应,在结点 \(u, v\) 之间连边的代价是它们在 \(T_1, T_2\) 上的树上路径长度之和。求结点两两连边形成的完全图的最小生成树权值。
\(2 \leq n \leq 10^5\)
思路
点分治 + 虚树。
首先套路地考虑在 \(T_2\) 上点分治,钦定有效路径过重心。
然后令 \(dep_u\) 表示结点 \(u\) 到分治重心的距离,那么在 \(u, v\) 之间连边的距离就是 \(dep_u + dep_v + T_1.dis(u, v)\),就变成了 AT3611.
这里暴力算 \(dis\) 的复杂度是错的,考虑先对当前分治重心的子树在 \(T_1\) 上建一棵虚树。
具体考虑的是把每个结点 \(u\) 拆成两份 \(u, u^{\prime} = u + n\),连长度为 \(dep_u\) 的边。令 \(u^{\prime}\) 为虚点。
考虑在虚树上跑一遍 dijkstra 求出距离每个结点 \(u\) 最近的虚点,记为 \(pre_u\)。考虑对于虚树上的每一条边 \((u, v)\),在 \(pre_u, pre_v\) 之间连一条长度为 \(dis_u + dis_v + w(u, v)\) 的边,其中 \(dis_u, dis_v\) 表示 \(u, v\) 到最近虚点的距离,\(w(u, v)\) 表示 \((u, v)\) 的边权。
实际上等价于钦定每一条边算它的贡献,这里感觉有点像边分治做这类套路的时候在虚树上枚举 LCA,但是具体不太会证。
然后跑一遍 kruskal 就行。
时间复杂度 \(O(n \log^2 n)\)
代码
#include <cstdio>
#include <vector>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
const int maxn = 6e5 + 5;
const int lg_sz = 20;
const int nd_sz = maxn * lg_sz;
const int inf = 1e9;
int n;
namespace IO
{
//by cyffff
int len = 0;
char ibuf[(1 << 20) + 1], *iS, *iT, out[(1 << 26) + 1];
#define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
#define reg register
inline int read()
{
reg char ch = gh();
reg int x = 0;
reg char t = 0;
while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
while (ch >= '0' && ch <= '9') x = x * 10 + (ch ^ 48), ch = gh();
return t ? -x : x;
}
inline void putc(char ch) { out[len++] = ch; }
template<class T>
inline void write(T x)
{
if (x < 0) putc('-'), x = -x;
if (x > 9) write(x / 10);
out[len++] = x % 10 + 48;
}
inline void flush()
{
fwrite(out, 1, len, stdout);
len = 0;
}
}
using IO::read;
using IO::write;
using IO::flush;
using IO::putc;
namespace kruskal
{
struct Edge
{
int u, v, w;
bool operator < (const Edge& rhs) const { return (w < rhs.w); }
} edge[nd_sz];
int tot;
int fa[nd_sz];
inline void add_edge(int u, int v, int w) { edge[++tot] = (Edge){u, v, w}; }
inline int get(int x) { return (fa[x] == x ? x : fa[x] = get(fa[x])); }
inline int kruskal()
{
int ans = 0;
sort(edge + 1, edge + tot + 1);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= tot; i++)
{
int fu = get(edge[i].u), fv = get(edge[i].v);
if (fu != fv) fa[fu] = fv, ans += edge[i].w;
}
return ans;
}
}
namespace tree
{
struct Edge
{
int to, nxt, w;
} edge[maxn];
int tot = 0;
int head[maxn], dep[maxn], dfn[maxn], lg[maxn], f[maxn][lg_sz];
inline void add_edge(int u, int v, int w)
{
edge[++tot] = (Edge){v, head[u], w};
head[u] = tot;
}
inline void dfs1(int u, int fa)
{
f[++tot][0] = u;
dfn[u] = tot;
// if (u == 25900) printf("%lld\n", 114514ll);
for (int i = head[u]; i; i = edge[i].nxt)
{
// if (u >= 25000) printf("caonima %lld %lld\n", u, head[u]);
int v = edge[i].to, d = edge[i].w;
// if (u == 25900) printf("%lld -> %lld : %lld\n", u, v, d);
if (v == fa) continue;
dep[v] = dep[u] + d;
// if (u == 25900) printf("%lld -> %lld : %lld\n", u, v, cnt);
dfs1(v, u);
f[++tot][0] = u;
}
}
inline int get_min(int a, int b) { return (dep[a] < dep[b] ? a : b); }
inline void init_st()
{
for (int i = 2; i <= tot; i++) lg[i] = lg[i >> 1] + 1;
for (int j = 1; (1 << j) <= tot; j++)
for (int i = 1; i + (1 << j) - 1 <= tot; i++)
f[i][j] = get_min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
inline int lca(int u, int v)
{
u = dfn[u], v = dfn[v];
if (u > v) swap(u, v);
int k = lg[v - u + 1];
return get_min(f[u][k], f[v - (1 << k) + 1][k]);
}
}
namespace vt
{
struct Edge
{
int to, nxt, w;
} edge[maxn];
int cnt, tot, top;
int head[maxn], q[maxn], stk[maxn], pre[maxn], dis[maxn];
bool vis[maxn];
inline void clear()
{
for (int i = 0; i <= max(cnt, tot); i++)
{
q[i] = stk[i] = 0;
head[edge[i].to] = 0;
}
cnt = tot = top = 0;
}
inline void add_edge(int u, int v, int d)
{
edge[++tot] = (Edge){v, head[u], d};
head[u] = tot;
}
inline bool cmp(int u, int v) { return tree::dfn[u] < tree::dfn[v]; }
inline void build()
{
sort(q + 1, q + cnt + 1, cmp);
stk[1] = q[1], top = 1;
for (int i = 2; i <= cnt; i++)
{
int p = tree::lca(stk[top], q[i]);
for ( ; (top > 1) && (tree::dep[p] <= tree::dep[stk[top - 1]]); top--)
{
add_edge(stk[top], stk[top - 1], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
add_edge(stk[top - 1], stk[top], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
}
if (top && (p != stk[top]))
{
add_edge(stk[top], p, tree::dep[stk[top]] - tree::dep[p]);
add_edge(p, stk[top], tree::dep[stk[top]] - tree::dep[p]);
stk[top] = p;
}
stk[++top] = q[i];
}
for ( ; top > 1; top--)
{
add_edge(stk[top], stk[top - 1], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
add_edge(stk[top - 1], stk[top], tree::dep[stk[top]] - tree::dep[stk[top - 1]]);
}
}
inline void dijkstra()
{
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
for (int i = 1; i <= tot; i++)
{
int v = edge[i].to;
if (v > n) pq.push(make_pair(0ll, v)), pre[v] = v - n, dis[v] = 0ll;
else dis[v] = inf;
vis[v] = false;
}
while (!pq.empty())
{
int u = pq.top().second;
pq.pop();
if (vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to, w = edge[i].w;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = pre[u];
pq.push(make_pair(dis[v], v));
}
}
}
}
inline void get_edge()
{
for (int i = 1; i <= tot; i += 2)
{
int u = edge[i].to, v = edge[i + 1].to;
if (pre[u] == pre[v]) continue;
kruskal::add_edge(pre[u], pre[v], dis[u] + dis[v] + edge[i].w);
}
}
}
namespace conquer
{
struct Edge
{
int to, nxt, w;
} edge[maxn];
int rt, tot;
int head[maxn], dep[maxn], sz[maxn], smx[maxn];
bool vis[maxn];
inline void add_edge(int u, int v, int d)
{
edge[++tot] = (Edge){v, head[u], d};
head[u] = tot;
}
inline void get_dep(int u, int fa)
{
vt::q[++vt::cnt] = u;
vt::add_edge(u, n + u, dep[u]);
vt::add_edge(n + u, u, dep[u]);
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to, d = edge[i].w;
if ((v == fa) || vis[v]) continue;
dep[v] = dep[u] + d;
get_dep(v, u);
}
}
inline void get_rt(int u, int fa, int sum)
{
sz[u] = 1, smx[u] = 0;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if ((v == fa) || vis[v]) continue;
get_rt(v, u, sum);
sz[u] += sz[v];
smx[u] = max(smx[u], sz[v]);
}
smx[u] = max(smx[u], sum - sz[u]);
if ((!rt) || (smx[u] < smx[rt])) rt = u;
}
inline void calc(int u)
{
vt::clear();
dep[u] = 0;
get_dep(u, 0);
vt::build();
vt::dijkstra();
vt::get_edge();
}
inline void solve(int u, int sum)
{
calc(u);
vis[u] = true;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (vis[v]) continue;
int s = (sz[v] < sz[u] ? sz[v] : sum - sz[u]);
rt = 0;
get_rt(v, u, s);
solve(rt, s);
}
}
}
signed main()
{
n = read();
// scanf("%lld", &n);
for (int i = 1, u, v, d; i <= n - 1; i++)
{
u = read(), v = read(), d = read();
// scanf("%lld%lld%lld", &u, &v, &d);
tree::add_edge(u, v, d);
tree::add_edge(v, u, d);
}
for (int i = 1, u, v, d; i <= n - 1; i++)
{
u = read(), v = read(), d = read();
// scanf("%lld%lld%lld", &u, &v, &d);
conquer::add_edge(u, v, d);
conquer::add_edge(v, u, d);
}
tree::tot = 0;
tree::dfs1(1, 0);
tree::init_st();
conquer::rt = 0;
conquer::get_rt(1, 0, n);
conquer::solve(conquer::rt, n);
// printf("%lld\n", kruskal::kruskal());
write(kruskal::kruskal()), putc('\n');
flush();
return 0;
}