[学习笔记] 树链剖分(重链剖分) - 图论 & 数据结构
树链剖分
树链剖分,用于解决一系列的树中链上问题的算法(数据结构)。其实对于树链修改和树链求和问题可以使用更加方便的树上差分解决,但是对于像求树链最大(小)权值之类的更复杂的问题,差分就显得不够用了。树链剖分大概就是把一颗树切分成为多条链,每一条链上节点的 \(dfn\) 都是顺序的,所以能够用线段树等高效的数据结构维护链上信息,复杂度不用说。唯一的缺点就是码量大,容易出错,所以很考验代码能力。
算法解析
树链剖分 有两种:重链剖分、长链剖分。重链剖分就是把子树最大的儿子成为 重儿子,把树分成若干条重链。重链剖分应用多,树剖一般都为重链剖分。
重链剖分可以将树上的任意一条路径划分成不超过 \(O(\log n)\) 条连续的链,每条链上的点深度互不相同(即是自底向上的一条链,链上所有点的 LCA 为链的一个端点)。
重链剖分还能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息。(下图来源于 OI-WIKI)
预处理
树链剖分有两次 DFS。每次 DFS 的复杂度都为 \(O(n)\)。
对于第一次 DFS,需要求出:
size[]
: 对应节点的子树大小(有多少节点)。fa[]
: 该节点的父亲节点。(根节点的父亲节点为自己)dep[]
: 节点深度。son[]
: 该节点的重儿子。
参考代码:
inline void dfs1(int u){
size[u] = 1, son[u] = -1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
dep[v] = dep[fa[v] = u] + 1;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[v] > size[son[u]]) son[u] = v;
}
}
}
第二次 DFS,需要求出:
top[]
: 该节点所在重链的链头。dfn[]
: 以重儿子为有限的 \(dfs\) 序。rnk[]
: 每个 \(dfs\) 序对应的节点编号。
为了构造 重链,就需要 重儿子 优先,所以每条重链都是由重儿子构成的,并且在重链内部 \(dfs\) 序是线性的。如果没有重儿子,那就走一条轻边继续建立重链。
不难发现,树链剖分的 \(dfn\) 同样满足根节点包含所有的子树节点的 \(dfn\)。如果有子树修改、子树查询的需要,维护一个 ed[]
数组,那么以某节点的子树区间即为 \([dfn[root],ed[root]]\)。
参考代码理解。
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t; //小小压行awa
// ed[u] = dfn[u] + size[u] - 1 如有需要则维护
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != fa[u] && v != son[u]) dfs2(v, v); //新一条重链
}
}
操作
求树链最值
下面展示最基础的 求树链最值(线段树) 的方法。因为我们已经把树链放到区间里去了,所以其他的大同小异。原理就是沿着 重链 不断向上跳,直到处于同一个重链上。这其实是一个求 LCA 的过程。
inline int qmax(int u, int v){
int tu = top[u], tv = top[v], ans = INT_MIN;
while(tu != tv){
if(dep[tu] >= dep[tv])
ans = max(ans, query_max(1, dfn[tu], dfn[u])), u = fa[tu]; //沿轻边向上跳
else
ans = max(ans, query_max(1, dfn[tv], dfn[v])), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v]) swap(u, v);
ans = max(ans, query_max(1, dfn[u], dfn[v]));
return ans;
}
树链剖分求LCA
常数极小,跑的飞快。\(O(n)-O(\log n)\)。
inline int Lca(int u, int v){
int tu = top[u], tv = top[v];
while(tu != tv){
if(dep[tu] >= dep[tv]) u = fa[tu], tu = top[u];
else v = fa[tv], tv = top[v];
}
return dfn[u]>dfn[v] ? v : u;
}
例题
[ZJOI2008] 树的统计 - 单点修改 树链查询
树链剖分板子,不多说了,代码注意细节就行。该用dfn的地方不要把点的编号传进去。链式前向星数组开两倍大。
#include<bits/stdc++.h>
using namespace std;
#define ls (id<<1)
#define rs (id<<1 | 1)
const int N = 3e4 + 1;
int n, h[N], cnt, val[N], q, son[N], size[N], dep[N], fa[N], dfn[N], top[N], rnk[N], tot;
struct edge{ int v, nxt; }e[N<<1];
struct node{ int l, r, mx, sum; }t[N<<2];
inline void add(int u, int v){ e[++cnt] = (edge){v, h[u]}; h[u] = cnt; }
inline void dfs1(int u){
size[u] = 1, son[u] = -1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
dep[v] = dep[u] + 1;
fa[v] = u;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[v] > size[son[u]]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[u] = t, rnk[dfn[u]=++tot] = u;
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != son[u] && v != fa[u]) dfs2(v, v);
}
}
inline void pushup(int id){
t[id].sum = t[ls].sum + t[rs].sum;
t[id].mx = max(t[ls].mx, t[rs].mx);
}
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){
t[id].mx = t[id].sum = val[rnk[l]];
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline void modify(int id, int v){
if(t[id].l == v && t[id].r == v){
t[id].sum = t[id].mx = val[rnk[v]];
return;
}
int mid = (t[id].l + t[id].r) >> 1;
modify(v<=mid?ls:rs, v);
pushup(id);
}
inline int query_sum(int id, int l, int r){
if(t[id].l >= l && r >= t[id].r) return t[id].sum;
int mid = (t[id].l + t[id].r) >> 1, ans = 0;
if(l <= mid) ans += query_sum(ls, l, r);
if(r > mid) ans += query_sum(rs, l, r);
return ans;
}
inline int query_max(int id, int l, int r){
if(t[id].l >= l && r >= t[id].r) return t[id].mx;
int mid = (t[id].l + t[id].r) >> 1, ans = INT_MIN;
if(l <= mid) ans = max(ans, query_max(ls, l, r));
if(r > mid) ans = max(ans, query_max(rs, l, r));
return ans;
}
inline int qsum(int u, int v){
int tu = top[u], tv = top[v], ans = 0;
while(tu != tv){
if(dep[tu] > dep[tv])
ans += query_sum(1, dfn[tu], dfn[u]), u = fa[tu];
else
ans += query_sum(1, dfn[tv], dfn[v]), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v]) ans += query_sum(1, dfn[v], dfn[u]);
else ans += query_sum(1, dfn[u], dfn[v]);
return ans;
}
inline int qmax(int u, int v){
int tu = top[u], tv = top[v], ans = INT_MIN;
while(tu != tv){
if(dep[tu] >= dep[tv])
ans = max(ans, query_max(1, dfn[tu], dfn[u])), u = fa[tu];
else
ans = max(ans, query_max(1, dfn[tv], dfn[v])), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v]) ans = max(ans, query_max(1, dfn[v], dfn[u]));
else ans = max(ans, query_max(1, dfn[u], dfn[v]));
return ans;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
for(int i=1, a, b; i<n; ++i) cin>>a>>b, add(a, b), add(b, a);
for(int i=1; i<=n; ++i) cin>>val[i];
dep[1] = 1, fa[1] = 1, dfs1(1); dfs2(1, 1);
build(1, 1, n);
cin>>q;
for(int i=1; i<=q; ++i){
string opt; int a, b;
cin>>opt>>a>>b;
if(opt == "CHANGE") val[a] = b, modify(1, dfn[a]); //It's very easy to make mistakes
else if(opt == "QMAX") cout<<qmax(a, b)<<'\n';
else cout<<qsum(a, b)<<'\n';
} return 0;
}
[SDOI2011] 染色 - 树链修改 树链查询
树链剖分的板子不用说,剩下的需要用线段树维护区间合法颜色数,该区间左端点和右端点的颜色,因为涉及区间修改和查询,所以还要维护 lazy_tag
。当且仅当左区间的右端点的颜色和右区间左端点的颜色一致时 --ans
即可。
#include<bits/stdc++.h>
using namespace std;
#define ls (id << 1)
#define rs (id << 1 | 1)
const int N = 1e5 + 1;
int q, n, m, val[N], h[N], cnt, dfn[N], son[N], size[N], fa[N], dep[N], top[N], rnk[N], tot;
struct edge{ int v, nxt; }e[N<<1];
struct node{ int l, r, num, lc, rc, tag; }t[N<<2];
inline void add(int a, int b){ e[++cnt] = (edge){b, h[a]}, h[a] = cnt; }
inline void addtag(int id, int v){ t[id] = (node){t[id].l, t[id].r, 1, v, v, v};}
inline void dfs1(int u){
size[u] = 1, son[u] = -1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
dep[v] = dep[fa[v] = u] + 1;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[v] > size[son[u]]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t;
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != fa[u] && v != son[u]) dfs2(v, v);
}
}
inline void pushup(int id){
t[id].num = t[ls].num + t[rs].num;
t[id].lc = t[ls].lc, t[id].rc = t[rs].rc;
if(t[ls].rc == t[rs].lc) --t[id].num;
}
inline void pushdown(int id){
if(t[id].tag != -1) addtag(ls, t[id].tag), addtag(rs, t[id].tag);
t[id].tag = -1;
}
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r, t[id].tag = -1;
if(l == r){ t[id].num = 1, t[id].lc = t[id].rc = val[rnk[l]]; return; }
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline void modify(int id, int l, int r, int v){
if(t[id].l >= l && t[id].r <= r){ addtag(id, v); return; }
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1;
if(l <= mid) modify(ls, l, r, v);
if(r > mid) modify(rs, l, r, v);
pushup(id);
}
inline int query(int id, int l, int r, int& lc, int& rc){
if(t[id].l >= l && t[id].r <= r){
lc = t[id].lc, rc = t[id].rc;
return t[id].num;
}
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1, ans = 0, lcd = -1, rcd = -1;
if(l <= mid) ans += query(ls, l, r, lc, rcd);
if(r > mid) ans += query(rs, l, r, lcd, rc);
if(rcd == lcd) --ans;
if(rcd == -1) lc = lcd;
if(lcd == -1) rc = rcd;
return ans;
}
inline int qcolor(int u, int v){
int tu = top[u], tv = top[v], adlc, adrc, ans1 = 0, ans2 = 0, lc1 = -1, lc2 = -1, rc1 = -1, rc2 = -1;
while(tu != tv){// printf("u = %d, v = %d\n", u, v);
if(dep[tu] > dep[tv]){
ans1 += query(1, dfn[tu], dfn[u], adlc, adrc);
if(lc1 != -1 && rc1 != -1 && rc1 == adrc) --ans1;
if(lc1 == -1) lc1 = adrc;
rc1 = adlc, u = fa[tu];
} else{ //(dfn) lc > rc > adrc > adlc
ans2 += query(1, dfn[tv], dfn[v], adlc, adrc);
if(lc2 != -1 && rc2 != -1 && rc2 == adrc) --ans2;
if(lc2 == -1) lc2 = adrc;
rc2 = adlc, v = fa[tv];
}
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v]){ // u adrc > adlc v
ans1 += query(1, dfn[v], dfn[u], adlc, adrc);
if(lc1 != -1 && rc1 != -1 && rc1 == adrc) --ans1;
if(lc2 != -1 && rc2 != -1 && rc2 == adlc) --ans2;
} else{ // v adrc > adlc u
ans1 += query(1, dfn[u], dfn[v], adlc, adrc);
if(lc1 != -1 && rc1 != -1 && rc1 == adlc) --ans1;
if(lc2 != -1 && rc2 != -1 && rc2 == adrc) --ans2;
} return ans1 + ans2;
}
inline void ccolor(int u, int v, int w){
int tu = top[u], tv = top[v];
while(tu != tv){
if(dep[tu] > dep[tv]) modify(1, dfn[tu], dfn[u], w), u = fa[tu];
else modify(1, dfn[tv], dfn[v], w), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v]) modify(1, dfn[v], dfn[u], w);
else modify(1, dfn[u], dfn[v], w);
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1; i<=n; ++i) cin>>val[i];
for(int i=1, a, b; i<n; ++i) cin>>a>>b, add(a, b), add(b, a);
dep[1] = fa[1] = 1, dfs1(1), dfs2(1, 1);
build(1, 1, n);
for(int i=1; i<=m; ++i){
char ch; int a, b, c;
cin>>ch>>a>>b;
if(ch == 'Q') cout<<qcolor(a, b)<<'\n';
else cin>>c, ccolor(a, b, c);
} return 0;
}
QTREE - Query on a tree - 单点修改 区间查询
看到权值在边,直接边权下放到节点,然后树链剖分板子。但考虑到权值在点,直接查询的话会多算上两节点的 \(LCA\) 的权值(但其实这条边根本没走),所以查询时要将左端点 +1。
query(1, dfn[u]+1, dfn[v])
#include<bits/stdc++.h>
using namespace std;
#define ls (id << 1)
#define rs (id << 1 | 1)
const int N = 1e4 + 1;
int n, h[N], cnt, val[N], dep[N], fa[N], size[N], son[N], top[N], dfn[N], rnk[N], tot, eg[N];
struct edge{ int v, nxt, val, num; }e[N<<1];
struct node{ int l, r, mx; }t[N<<2];
inline void add(int u, int v, int w, int i){ e[++cnt] = (edge){v, h[u], w, i}; h[u] = cnt; }
inline void dfs1(int u){
size[u] = 1, son[u] = -1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
val[eg[e[i].num] = v] = e[i].val;
dep[v] = dep[fa[v] = u] + 1;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[son[u]] < size[v]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t;
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != fa[u] && v != son[u]) dfs2(v, v);
}
}
inline void pushup(int id){ t[id].mx = max(t[ls].mx, t[rs].mx); }
inline void build(int id, int l ,int r){
t[id].l = l, t[id].r = r;
if(l == r){ t[id].mx = val[rnk[l]]; return; }
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline void modify(int id, int u, int v){
if(t[id].l == u && t[id].r == u){ t[id].mx = v; return; }
int mid = (t[id].l + t[id].r) >> 1;
modify(u<=mid?ls:rs, u, v);
pushup(id);
}
inline int query(int id, int l, int r){
if(l > r) return 0;
if(l <= t[id].l && t[id].r <= r) return t[id].mx;
int mid = (t[id].l + t[id].r) >> 1, ans = INT_MIN;
if(l <= mid) ans = max(ans, query(ls, l, r));
if(r > mid) ans = max(ans, query(rs, l, r));
return ans;
}
inline int qtree(int u, int v){
int tu = top[u], tv = top[v], ans = INT_MIN;
while(tu != tv){
if(dep[tu] >= dep[tv])
ans = max(ans, query(1, dfn[tu], dfn[u])), u = fa[tu];
else
ans = max(ans, query(1, dfn[tv], dfn[v])), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v])
ans = max(ans, query(1, dfn[v]+1, dfn[u])); // very important
else
ans = max(ans, query(1, dfn[u]+1, dfn[v])); // very important
return ans;
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
for(int i=1, a, b, c; i<n; ++i) cin>>a>>b>>c, add(a, b, c, i), add(b, a, c, i);
fa[1] = dep[1] = 1, dfs1(1), dfs2(1, 1);
build(1, 1, n);
string s; int a, b;
while(cin>>s && s != "DONE"){
cin>>a>>b;
if(s == "QUERY") cout<<qtree(a, b)<<'\n';
else modify(1, dfn[eg[a]], b);
} return 0;
}
[HAOI2015] 树上操作 - 单点修改 子树修改 树链查询
板子。因为根节点的 \(dfn\) 一定小于子节点的 \(dfn\),所以只需要维护每个节点的 \(end\) 值(子节点最大 \(dfn\) 值),最后修改 \(dfn[u]->ed[u]\) 这个区间就行了。因为涉及区间修改,需要维护 lazy_tag
。树链查询用树链剖分即可。
另:本题也可用树上查分解决。
#include<bits/stdc++.h>
using namespace std;
#define ls (id << 1)
#define rs (id << 1 | 1)
#define int long long
const int N = 1e5 + 1;
int n, m, h[N], cnt, val[N], dep[N], son[N], size[N], fa[N], ed[N], top[N], dfn[N], rnk[N], tot;
struct edge{ int v, nxt; }e[N<<1];
struct node{ int l, r, sum, tag; }t[N<<2];
inline void add(int u, int v){ e[++cnt] = (edge){v, h[u]}, h[u] = cnt; }
inline void addtag(int id, int v){
t[id].tag += v;
t[id].sum += v*(t[id].r - t[id].l + 1);
}
inline void pushup(int id){ t[id].sum = t[ls].sum + t[rs].sum; }
inline void pushdown(int id){
if(t[id].tag != 0) addtag(ls, t[id].tag), addtag(rs, t[id].tag);
t[id].tag = 0;
}
inline void dfs1(int u){
size[u] = 1, son[u] = -1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
dep[v] = dep[fa[v]=u] + 1;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[v] > size[son[u]]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t;
if(son[u] == -1){ ed[u] = tot; return; }
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != fa[u] && v != son[u]) dfs2(v, v);
} ed[u] = tot;
}
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){ t[id].sum = val[rnk[l]]; return; }
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline void modify(int id, int l, int r, int v){
if(l <= t[id].l && t[id].r <= r){ addtag(id, v); return; }
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1;
if(l <= mid) modify(ls, l, r, v);
if(r > mid) modify(rs, l, r, v);
pushup(id);
}
inline int query(int id, int l, int r){
if(l <= t[id].l && t[id].r <= r) return t[id].sum;
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1, ans = 0;
if(l <= mid) ans += query(ls, l, r);
if(r > mid) ans += query(rs, l, r);
return ans;
}
inline int qtree(int u){
int tu = top[u], ans = 0;
while(tu != 1) ans += query(1, dfn[tu], dfn[u]), u = fa[tu], tu = top[u];
ans += query(1, 1, dfn[u]);
return ans;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1; i<=n; ++i) cin>>val[i];
for(int i=1, a, b; i<n; ++i) cin>>a>>b, add(a, b), add(b, a);
fa[1] = dep[1] = 1, dfs1(1); dfs2(1, 1);
build(1, 1, n);
for(int i=1, a, b, c; i<=m; ++i){
cin>>a>>b;
if(a == 1) cin>>c, modify(1, dfn[b], dfn[b], c);
if(a == 2) cin>>c, modify(1, dfn[b], ed[b], c);
if(a == 3) cout<<qtree(b)<<'\n';
} return 0;
}
[NOIP2013 提高组] 货车运输
- 法一:生成树 + 树链剖分。利用最大生成树筛掉不合格(非最优)的边。然后在新生成的树上用树链剖分求解。
#include<bits/stdc++.h>
using namespace std;
#define ls (id << 1)
#define rs (id << 1 | 1)
const int N = 1e4 + 1, M = 5e4 + 3;
int n, m, q, root[N], h[N], cnt, val[N], f[N], fa[N], son[N], size[N], dep[N], dfn[N], rnk[N], top[N], tot;
struct road{ int u, v, w; }rd[M];
struct edge{ int v, nxt, val; }e[N<<1];
struct node{ int l, r, mn; }t[N<<2];
inline bool cmp(road a, road b){ return a.w > b.w; }
inline int find(int k){
if(!f[k]) return k;
return f[k] = find(f[k]);
}
inline void add(int u, int v, int val){ e[++cnt] = (edge){v, h[u], val}, h[u] = cnt; }
inline void kruskal(){
sort(rd+1, rd+m+1, cmp);
int eg = 0;
for(int i=1; i<=m; ++i){
if(eg == n-1) break;
int fa = find(rd[i].u), fb = find(rd[i].v);
if(fa == fb) continue;
++eg, f[fa] = fb;
add(rd[i].u, rd[i].v, rd[i].w), add(rd[i].v, rd[i].u, rd[i].w);
}
}
inline void dfs1(int u, int rt){
size[u] = 1, son[u] = -1, root[u] = rt;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
val[v] = e[i].val;
dep[v] = dep[fa[v] = u] + 1;
dfs1(v, rt);
size[u] += size[v];
if(son[u] == -1 || size[v] > size[son[u]]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t;
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != son[u] && v != fa[u]) dfs2(v, v);
}
}
inline void pushup(int id){ t[id].mn = min(t[ls].mn, t[rs].mn); }
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){ t[id].mn = val[rnk[l]]; return; }
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline int query(int id, int l, int r){
if(l > r) return INT_MAX;
if(l <= t[id].l && t[id].r <= r) return t[id].mn;
int mid = (t[id].l + t[id].r) >> 1, ans = INT_MAX;
if(l <= mid) ans = min(ans, query(ls, l ,r));
if(r > mid) ans = min(ans, query(rs, l, r));
return ans;
}
inline int qtree(int u, int v){
int tu = top[u], tv = top[v], ans = INT_MAX;
while(tu != tv){
if(dep[tu] > dep[tv])
ans = min(ans, query(1, dfn[tu], dfn[u])), u = fa[tu];
else
ans = min(ans, query(1, dfn[tv], dfn[v])), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] >= dfn[v])
ans = min(ans, query(1, dfn[v]+1, dfn[u]));
else
ans = min(ans, query(1, dfn[u]+1, dfn[v]));
return ans;
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1; i<=m; ++i) cin>>rd[i].u>>rd[i].v>>rd[i].w;
kruskal();
for(int i=1; i<=n; ++i)
if(!dep[i]) dep[i] = 1, fa[i] = i, dfs1(i, i);
for(int i=1; i<=n; ++i)
if(!dfn[i]) dfs2(i, i);
build(1, 1, n);
cin>>q;
for(int i=1, a, b; i<=q; ++i){
cin>>a>>b;
if(root[a] != root[b]) cout<<"-1\n";
else cout<<qtree(a, b)<<'\n';
} return 0;
}
- 法二:最大生成树 + 倍增。就是在倍增求 LCA 的同时求出所走路径中最小值。比上个方法能快一些。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 1, M = 5e4 + 1;
int n, m, q, f[N], cnt, h[N], fa[21][N], wg[21][N], deep[N], scc[N], lg[N];
bitset<N> flag;
struct Edge{ int u, v, dt; }E[M];
struct edge{ int v, nt, dt; }e[N];
bool cmp(Edge a, Edge b){ return a.dt > b.dt; }
inline void add(int u, int v, int dt){ e[++cnt] = (edge){v, h[u], dt}; h[u] = cnt;}
inline int find(int k){
if(!f[k]) return k;
return f[k] = find(f[k]);
}
inline void kruskal(){
sort(E+1, E+m+1, cmp);
int eg = 0;
for(int i=1; i<=m; ++i){
if(eg == n-1) break;
int fa = find(E[i].u), fb = find(E[i].v);
if(fa == fb) continue;
else{
++eg;
f[fa] = fb;
add(E[i].u, E[i].v, E[i].dt), add(E[i].v, E[i].u, E[i].dt);
}
}
}
inline void dfs(int k, int f){
scc[k] = scc[f];
flag[k] = 1;
deep[k] = deep[f] + 1;
for(int i=h[k]; i; i=e[i].nt){
int v = e[i].v;
if(!flag[v]){
wg[0][v] = e[i].dt;
fa[0][v] = k;
dfs(v, k);
}
}
}
inline int getans(int a, int b){
int ans = INT_MAX;
if(deep[a] < deep[b]) swap(a, b);
while(deep[a] != deep[b]){
ans = min(ans, wg[lg[deep[a]-deep[b]]][a]);
a = fa[lg[deep[a]-deep[b]]][a];
}
if(a == b) return ans;
for(int i=lg[deep[a]]; i>=0; --i)
if(fa[i][a] != fa[i][b]){
ans = min({ans, wg[i][a], wg[i][b]});
a = fa[i][a], b = fa[i][b];
}
return min({ans, wg[0][a], wg[0][b]});
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
lg[0] = -1;
for(int i=1; i<=n; ++i) lg[i] = lg[i>>1] + 1;
for(int i=1; i<=m; ++i) cin>>E[i].u>>E[i].v>>E[i].dt;
kruskal();
deep[0] = -1;
for(int i=1; i<=n; ++i){
if(!flag[i]){
++scc[0];
dfs(i, 0);
fa[0][i] = i;
wg[0][i] = INT_MAX;
}
}
for(int i=1; i<=20; ++i){
for(int j=1; j<=n; ++j){
fa[i][j] = fa[i-1][fa[i-1][j]];
wg[i][j] = min(wg[i-1][j], wg[i-1][fa[i-1][j]]);
}
}
cin>>q;
for(int i=1, a, b; i<=q; ++i){
cin>>a>>b;
if(scc[a] != scc[b]) cout<<"-1\n";
else cout<<getans(a, b)<<'\n';
}
return 0;
}
P2680 [NOIP2015 提高组] 运输计划
非常巧妙的 补集思想。我不会,看的大佬的解子。
#include<bits/stdc++.h>
using namespace std;
#define ls (id << 1)
#define rs (id << 1 | 1)
const int N = 3e5 + 1;
int n, m, h[N], cnt, tot, dep[N], son[N], size[N], fa[N], dfn[N], top[N], rnk[N], val[N], chmx = -1, cu, cv;
struct edge{ int v, nxt, val; }e[N<<1];
inline void add(int u, int v, int w){ e[++cnt] = (edge){v, h[u], w}; h[u] = cnt; }
struct SegmentTree1{
struct edge{ int l, r, sum; }t[N<<2];
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){ t[id].sum = val[rnk[l]]; return; }
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
t[id].sum = t[ls].sum + t[rs].sum;
}
inline int query(int id, int l, int r){
if(l > r) return 0;
if(l <= t[id].l && t[id].r <= r) return t[id].sum;
int mid = (t[id].l + t[id].r) >> 1, ans = 0;
if(l <= mid) ans += query(ls, l, r);
if(r > mid) ans += query(rs, l, r);
return ans;
}
}SUM;
struct SegmentTree2{
struct edge{ int l, r, mx, tag; }t[N<<2];
inline void addtag(int id, int v){
t[id].mx = max(t[id].mx, v);
t[id].tag = max(t[id].tag, v);
}
inline void pushup(int id){ t[id].mx = max(t[ls].mx, t[rs].mx); }
inline void pushdown(int id){
if(t[id].tag != -1) addtag(ls, t[id].tag), addtag(rs, t[id].tag);
t[id].tag = -1;
}
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r, t[id].tag = -1;
if(l == r){ t[id].mx = 0; return; }
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid+1, r);
pushup(id);
}
inline void modify(int id, int l, int r, int v){
if(l > r) return;
if(l <= t[id].l && t[id].r <= r){ addtag(id, v); return; }
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1;
if(l <= mid) modify(ls, l, r, v);
if(r > mid) modify(rs, l, r, v);
pushup(id);
}
inline int query(int id, int u){
if(t[id].l == u && t[id].r == u) return t[id].mx;
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1;
return query(u<=mid?ls:rs, u);
}
}MAX;
inline void dfs1(int u){
son[u] = -1, size[u] = 1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
val[v] = e[i].val;
dep[v] = dep[fa[v] = u] + 1;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[son[u]] < size[v]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t;
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != son[u] && v != fa[u]) dfs2(v, v);
}
}
struct Itv{ int l, r; };
inline bool cmp(Itv a, Itv b){ return a.l < b.l; }
inline int Update(int u, int v){
int tu = top[u], tv = top[v], ans = 0, p = 0;
Itv itv[21];
while(tu != tv){
if(dep[tu] > dep[tv]){
ans += SUM.query(1, dfn[tu], dfn[u]);
itv[++p] = (Itv){dfn[tu], dfn[u]};
u = fa[tu];
} else{
ans += SUM.query(1, dfn[tv], dfn[v]);
itv[++p] = (Itv){dfn[tv], dfn[v]};
v = fa[tv];
}
tu = top[u], tv = top[v];
}
if(dfn[u] > dfn[v]) swap(u, v);
ans += SUM.query(1, dfn[u]+1, dfn[v]);
itv[++p] = (Itv){dfn[u]+1, dfn[v]};
sort(itv+1, itv+1+p, cmp);
MAX.modify(1, 2, itv[1].l-1, ans);
for(int i=1; i<p; ++i)
MAX.modify(1, itv[i].r+1, itv[i+1].l-1, ans);
MAX.modify(1, itv[p].r+1, n, ans);
return ans;
}
inline int get_ans(int u, int v){
int tu = top[u], tv = top[v], ans = INT_MAX;
if(u == v) return 0;
while(tu != tv){
if(dep[tu] > dep[tv])
while(u != fa[tu]) ans = min(ans, max(chmx-val[u], MAX.query(1, dfn[u]))), u = fa[u];
else
while(v != fa[tv]) ans = min(ans, max(chmx-val[v], MAX.query(1, dfn[v]))), v = fa[v];
tu = top[u], tv = top[v];
}
if(dfn[u] < dfn[v]) swap(u, v);
while(u != v) ans = min(ans, max(chmx-val[u], MAX.query(1, dfn[u]))), u = fa[u];
return ans;
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1, a, b, c; i<n; ++i){
cin>>a>>b>>c;
add(a, b, c), add(b, a, c);
}
dep[1] = fa[1] = 1, dfs1(1); dfs2(1, 1);
SUM.build(1, 1, n); MAX.build(1, 1, n);
for(int i=1, a, b; i<=m; ++i){
cin>>a>>b;
int chnv = Update(a, b);
if(chnv > chmx) chmx = chnv, cu = a, cv = b;
} return cout<<get_ans(cu, cv), 0;
}
遥远的国度 换根 + 树链修改 + 子树求最值
树链修改和子树求最值不说,都是板子。对于换根,考虑三种情况:
- 根是自己:直接求全部最小值即可。
- 根是父节点:不变,按照开始时的根计算即可。
- 根是子节点:找到这个子节点所在的最大的不包含父节点的树,也就是与父节点直接相连的儿子节点里面,谁的子树包含了根,谁就是那棵特别的树,求最值的时候把特别的树所包含的区间剔除出去就好了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 1;
int n, m, rt, h[N], cnt, tot, val[N], dep[N], fa[N], size[N], son[N], dfn[N], rnk[N], ed[N], top[N];
struct edge{ int v, nxt; }e[N<<1];
inline void add(int u, int v){ e[++cnt] = (edge){v, h[u]}, h[u] = cnt; }
inline void dfs1(int u){
son[u] = -1, size[u] = 1;
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(!dep[v]){
dep[v] = dep[fa[v] = u] + 1;
dfs1(v);
size[u] += size[v];
if(son[u] == -1 || size[v] > size[son[u]]) son[u] = v;
}
}
}
inline void dfs2(int u, int t){
top[rnk[dfn[u]=++tot] = u] = t;
ed[u] = dfn[u] + size[u] - 1;
if(son[u] == -1) return;
dfs2(son[u], t);
for(int i=h[u]; i; i=e[i].nxt){
int v = e[i].v;
if(v != fa[u] && v != son[u]) dfs2(v, v);
}
}
struct SegmentTree{
#define ls (id << 1)
#define rs (id << 1 | 1)
struct node{ int l, r, mn, tag; }t[N<<2];
inline void pushup(int id){ t[id].mn = min(t[ls].mn, t[rs].mn); }
inline void addtag(int id, int v){ t[id].tag = v, t[id].mn = v; }
inline void pushdown(int id){
if(t[id].tag) addtag(ls, t[id].tag), addtag(rs, t[id].tag);
t[id].tag = 0;
}
inline void build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){ t[id].mn = val[rnk[l]]; return; }
int mid = (t[id].l + t[id].r) >> 1;
build(ls, l ,mid), build(rs, mid+1, r);
pushup(id);
}
inline void modify(int id, int l, int r, int v){
if(l <= t[id].l && t[id].r <= r){ addtag(id, v); return; }
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1;
if(l <= mid) modify(ls, l, r, v);
if(r > mid) modify(rs, l, r, v);
pushup(id);
}
inline int query(int id, int l, int r){
if(l > r) return INT_MAX;
if(l <= t[id].l && t[id].r <= r) return t[id].mn;
pushdown(id);
int mid = (t[id].l + t[id].r) >> 1, ans = INT_MAX;
if(l <= mid) ans = min(ans, query(ls, l, r));
if(r > mid) ans = min(ans, query(rs, l, r));
return ans;
}
}ST;
inline void mtree(int u, int v, int w){
int tu = top[u], tv = top[v];
while(tu != tv){
if(dep[tu] > dep[tv])
ST.modify(1, dfn[tu], dfn[u], w), u = fa[tu];
else
ST.modify(1, dfn[tv], dfn[v], w), v = fa[tv];
tu = top[u], tv = top[v];
}
if(dfn[u] > dfn[v]) swap(u, v);
ST.modify(1, dfn[u], dfn[v], w);
}
inline int qtree(int u){
if(dfn[u] < dfn[rt] && dfn[rt] <= ed[u]){
int v = rt, tv = top[rt];
while(tv != top[u] && fa[tv] != u) tv = top[v = fa[tv]]; // need spj
while(dep[fa[v]] > dep[u]) v = fa[v];
return min(ST.query(1, 1, dfn[v]-1), ST.query(1, ed[v]+1, n));
} else if(rt == u) return ST.query(1, 1, n); // need spj
else return ST.query(1, dfn[u], ed[u]);
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1, a, b; i<n; ++i) cin>>a>>b, add(a, b), add(b, a);
for(int i=1; i<=n; ++i) cin>>val[i];
cin>>rt;
fa[rt] = dep[rt] = rt, dfs1(rt); dfs2(rt, rt);
ST.build(1, 1, n);
for(int i=1, opt, a, b, c; i<=m; ++i){
cin>>opt>>a;
if(opt == 1) rt = a;
else if(opt == 3) cout<<qtree(a)<<'\n';
else cin>>b>>c, mtree(a, b, c);
} return 0;
}