算法学习:边分治
前言
本来作为一名标准的NOIP退役选手,老早想要再见OI+停更+读文化课一波行云流水的操作了,结果D类居然还有,于是继续苟!然后就有了日常赛前学算法。
例题
bzoj2870: 最长道路tree
权限题来着,题目大意如下:
给定一棵N个点的树,求树上一条链使得链的长度乘链上所有点中的最小权值所得的积最大。
其中链长度定义为链上点的个数。
分析
关于树上路径问题,想必大家都清楚著名的点分治算法(否则就不会学什么边分治了),然而这道题如果采用点分治的话也不是不行,就是会疯掉。子树合并的时候很难搞。
这时候有一种就要采用边分治。边分治作为点分治的替代品,有非常优秀的性质,等会会提到。下面先说明边分治的过程。
边分治
点分治是每一次采用树的重心进行分治,换而言之就是最大子树最小的,那么边分治类比一下,每次寻找边连接的两个子树最大值最小的那天边,更形式化地表述,寻找 m i n { ( u , v ) ∈ E ∣ m a x ( s i z e u , s i z e v ) } min\{(u,v)\in E|max(size_u,size_v)\} min{(u,v)∈E∣max(sizeu,sizev)}。
void Rt(int u, int fa) {
sz[u] = 1; cnt += (u <= n);
for(int i = T.pr[u], v; i; i = T.nx[i])
if(!del[i >> 1] && (v = T.to[i]) != fa) {
Rt(v, u), sz[u] += sz[v];
int tmp = std::max(sz[v], sums - sz[v]);
if(mn > tmp) mn = tmp, rt = i;
}
}
这个算法的复杂度有保证吗?链卡一卡?菊花图卡一卡?菊花图卡掉了!
于是我们需要改进一下这个算法。
知识点:多叉树转二叉树
我们发现链卡不掉,二叉树也卡不掉。
于是我们考虑用左儿子右兄弟表示法转多叉树。
也就是把原本
u
→
v
i
∣
i
∈
[
1
,
n
]
u\to v_i|i\in [1,n]
u→vi∣i∈[1,n]
变成
u
→
v
1
,
v
i
→
v
i
+
1
∣
i
∈
[
1
,
n
)
u\to v_1,v_i \to v_{i+1}|i\in [1,n)
u→v1,vi→vi+1∣i∈[1,n)
但是这个方法有一个弊端,就是我们就连两点间距离这个树的最基本特征都改变了。
有一种神奇的方法可以保留这个性质。
新建节点
x
1
,
x
2
,
⋯
x
n
x_1,x_2,\cdots x_n
x1,x2,⋯xn
然后这样连
u
→
x
1
,
x
i
→
x
i
+
1
∣
i
∈
[
1
,
n
)
,
x
i
→
w
i
∣
i
∈
[
1
,
n
]
u\to x_1,x_i \to x_{i+1}|i\in [1,n),x_i\to w_i|i\in [1,n]
u→x1,xi→xi+1∣i∈[1,n),xi→wi∣i∈[1,n]
对于
(
u
,
x
)
(u,x)
(u,x),
(
x
,
x
)
(x,x)
(x,x)之间的边权为0,然后把
(
u
,
w
i
)
(u,w_i)
(u,wi)的边权转移到
(
x
i
,
w
i
)
(x_i,w_i)
(xi,wi)上。
这样的话就OK啦。
void Ins(int u, int v) {
++tot; T.adds(tot, v, 1); ::v[tot] = ::v[lst[u]];
T.adds(lst[u], tot, 0); lst[u] = tot;
}
void Build(int u, int fa) {
for(int i = G.pr[u], v; i;i = G.nx[i])
if((v = G.to[i]) != fa)
Ins(u, v), Build(v, u);
}
例题
边分治有啥好处嘞?
你会发现,它分治的时候只有两棵子树。
这样多子树合并变成了双子树合并。
对于这道题而言,考虑把经过这条边的路径合并。
我们把两边子树的从根出发的路径都提出来,这样的话问题转化成:
每条链有长度和权值两个属性,把两个链合并得到的是权值的最小值乘上长度和。
按照权值排序。双指针扫一遍即可。
代码
很好写滴。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp std::make_pair
const int N = 1e5 + 10, M = 2e5 + 10, inf = 1e9;
typedef std::pair<int, int> pa;
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);}
}G, T;
int tot, lst[N], sz[N], v[N], top[2], cnt, sums, rt, n, mn;
bool del[N]; pa st[2][N]; long long ans;
void Ins(int u, int v) {
++tot; T.adds(tot, v, 1); ::v[tot] = ::v[lst[u]];
T.adds(lst[u], tot, 0); lst[u] = tot;
}
void Build(int u, int fa) {
for(int i = G.pr[u], v; i;i = G.nx[i])
if((v = G.to[i]) != fa)
Ins(u, v), Build(v, u);
}
void Rt(int u, int fa) {
sz[u] = 1; cnt += (u <= n);
for(int i = T.pr[u], v; i; i = T.nx[i])
if(!del[i >> 1] && (v = T.to[i]) != fa) {
Rt(v, u), sz[u] += sz[v];
int tmp = std::max(sz[v], sums - sz[v]);
if(mn > tmp) mn = tmp, rt = i;
}
}
void Dfs(int u, int fa, int len, int mn, int c) {
mn = std::min(v[u], mn); st[c][++top[c]] = mp(mn, len);
for(int i = T.pr[u], v; i;i = T.nx[i])
if(!del[i >> 1] && (v = T.to[i]) != fa)
Dfs(v, u, len + T.w[i], mn, c);
}
void Get(int c, int w) {
pa *f = st[c], *g = st[c ^ 1];
for(int i = top[c], j = top[c ^ 1], mx = 0; i; --i) {
for(;j && g[j].fi >= f[i].fi;)
mx = std::max(mx, g[j--].se);
if(j < top[c ^ 1])
ans = std::max(ans, 1LL * 1LL * (mx + f[i].se + w + 1) * f[i].fi);
}
}
void Div(int u, int pres) {
if(pres == 1) return ;
mn = inf; sums = pres; Rt(u, 0);
del[rt >> 1] = true;
top[0] = top[1] = 0;
Dfs(T.to[rt], 0, 0, inf, 0);
Dfs(T.to[rt ^ 1], 0, 0, inf, 1);
std::sort(st[0] + 1, st[0] + top[0] + 1);
std::sort(st[1] + 1, st[1] + top[1] + 1);
Get(0, T.w[rt]); Get(1, T.w[rt]);
int nw = rt, S2 = pres - sz[T.to[rt]];
Div(T.to[nw], sz[T.to[rt]]); Div(T.to[nw ^ 1], S2);
}
int main() {
n = ri();
for(int i = 1;i <= n; ++i)
v[i] = ri();
for(int i = 1;i < n; ++i)
G.adds(ri(), ri());
for(int i = 1;i <= n; ++i)
lst[i] = i;
tot = n; Build(1, 0);
Div(1, tot);
printf("%lld\n", ans);
return 0;
}