[学习笔记] 树链剖分(重链剖分) - 图论 & 数据结构
树链剖分
树链剖分,用于解决一系列的树中链上问题的算法(数据结构)。其实对于树链修改和树链求和问题可以使用更加方便的树上差分解决,但是对于像求树链最大(小)权值之类的更复杂的问题,差分就显得不够用了。树链剖分大概就是把一颗树切分成为多条链,每一条链上节点的 \(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;
}
[学习笔记] 树链剖分(重链剖分) - 图论 & 数据结构的更多相关文章
- HDU 3966 Aragorn's Story (树链点权剖分,成段修改单点查询)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3966 树链剖分的模版,成段更新单点查询.熟悉线段树的成段更新的话就小case啦. //树链剖分 边权修 ...
- GO语言学习笔记-工具链篇 Study for Go ! Chapter eleven - Tool Chain
持续更新 Go 语言学习进度中 ...... GO语言学习笔记-类型篇 Study for Go! Chapter one - Type - slowlydance2me - 博客园 (cnblogs ...
- C语言数据结构基础学习笔记——树
树是一种一对多的逻辑结构,树的子树之间没有关系. 度:结点拥有的子树数量. 树的度:树中所有结点的度的最大值. 结点的深度:从根开始,自顶向下计数. 结点的高度:从叶结点开始,自底向上计数. 树的性质 ...
- Java-马士兵设计模式学习笔记-责任链模式-FilterChain功能
一.目标 增加filterchain功能 二.代码 1.Filter.java public interface Filter { public String doFilter(String str) ...
- Java-J2SE学习笔记-树状展现文件结构
1.利用java.io相关类树状展现文件结构 2.判定给定路径是否为dir,是则递归,每一递归一层缩进一次 3.代码 package Test; import java.io.File; public ...
- Java-马士兵设计模式学习笔记-责任链模式-模拟处理Reques Response
一.目标 1.用Filter模拟处理Request.Response 2.思路细节技巧: (1)Filter的doFilter方法改为doFilter(Request,Resopnse,FilterC ...
- 学习笔记——责任链模式ChainOfResponsibility
责任链模式,主要是通过自己记录一个后继者来判断当前的处理情况.Handler中,再增加一个方法用于设置后继对象,如SetHandler(Handler obj). 然后Handler类以其子类的处理方 ...
- 【C++学习笔记】 链式前向星
链式前向星是一种常见的储存图的方式(是前向星存图法的优化版本),支持增边和查询,但不支持删边(如果想要删除指定的边建议用邻接矩阵). 储存方式 首先定义数组 head[ i ] 来储存从节点 i 出发 ...
- [学习笔记] 舞蹈链(DLX)入门
"在一个全集\(X\)中若干子集的集合为\(S\),精确覆盖(\(\boldsymbol{Exact~Cover}\))是指,\(S\)的子集\(S*\),满足\(X\)中的每一个元素在\( ...
- Java-马士兵设计模式学习笔记-责任链模式-处理数据
一.目标 数据提交前做各种处理 二.代码 1.MsgProcessor.java public class MsgProcessor { private List<Filter> filt ...
随机推荐
- Python 潮流周刊#56:NumPy 2.0 里更快速的字符串函数(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- 数据库阻塞查询语句 SQL SERVER
--查询CPU占用最高的SQL语句 SELECT total_worker_time/execution_count AS avg_cpu_cost, plan_handle, execution_c ...
- Linux 增加 swap 分区
检查当前swap分区 [root@localhost ~]# free -g total used free shared buffers cached Mem: 15 0 14 0 0 0 -/+ ...
- 如何用matlab求隐式函数的导数
如何用matlab求隐式函数的导数 隐函数求导的例子 假设有一个圆 \(x^2+y^2=5\) , 要求在某个点上的切线的斜率. 我们可以把式\(x^2+y^2=5\)中的每一项对\(x\)求导, 可 ...
- 对Transformer的一些理解
在学习Transformer这个模型前对seq2seq架构有个了解时很有必要的 先上图 输入和输出 首先理解模型时第一眼应该理解输入和输出最开始我就非常纠结 有一个Inputs,一个Outputs(s ...
- Quarkus初体验:动态加载和原生部署
在前面的文章<尝试官方的第一个SpringNative 0.11程序>中提到过Quarkus这门技术.这里就简单演示一下它的两个主要功能:Live Coding和Native Build. ...
- npm基本操作手册
查看npm版本 npm -v 设置仓库地址 # 默认仓库地址 npm config set registry https://registry.npmjs.org/ # 淘宝镜像地址 npm conf ...
- oeasy教您玩转vim - 31 - # 文字区块
文字区块 回忆上节课内容 上上次讲的翻页 上次先让屏幕位置固定,移动光标 H- Head 移动到屏幕的顶端 M- Middle 移动到屏幕的中间 L- Low 移动到屏幕的底部 然后让光标固定,移 ...
- ICPC游记
\[\Large\color{#FCAEBD}『2024ICPC河南站 游记』 \] Day 0 晚上打了场 \(ABC\),快成屎了,最后竟然还加分了. 晚上回家洗了个澡,收拾收拾东西,凌晨2点就睡 ...
- AT_agc017_b 题解
洛谷链接&Atcoder 链接 本篇题解为此题较简单做法,请放心阅读. 题目简述 一共有 \(n\) 个格子,给定两个整数 \(A,B\) 分别位于第 \(1\) 和第 \(n\) 格,中间有 ...