dsu on tree

本文介绍了dsuontree算法,一种适用于解决无修改子树询问问题的技术。通过轻重链剖分,该算法能在O(nlogn)时间内高效处理问题。文章提供了多个示例代码,包括CodeForces平台上的题目解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

osu on tree?

dsu on tree!

这种操作可以在$O(nlogn)$的时间内解决一些无修改子树询问问题。

咱知道把一棵树轻重链剖分后,树的轻链,重链都只有$O(logn)$个。

这个算法就是利用了这一点,递归处理时保留重儿子的信息,轻儿子的则重新计算。

乍一看感觉很暴力,但是实际上是$O(nlogn)$的。

来看几道题吧。

CodeForces - 600E

 1 #include<cstdio>
 2 #include<vector>
 3 #include<bitset>
 4 #include<iostream>
 5 #define pb push_back
 6 #define nc getchar
 7 using namespace std;
 8 inline void read(int &x) {
 9     char b = nc(); x = 0;
10     for (; !isdigit(b); b = nc());
11     for (; isdigit(b); b = nc()) x = x * 10 + b - '0';
12 }
13 vector < int > g[100005];
14 inline void aE(int u, int v) {
15     g[u].pb(v); g[v].pb(u);    
16 }
17 int n, rt, c[100005], sz[100005], mx, cnt[100005], son[100005];
18 long long ans[100005], tans;
19 bitset < 100005 > h;
20 void dfz(int u, int f) {
21     sz[u] = 1; for (int v, i = 0; i < g[u].size(); ++i) if ((v = g[u][i]) != f) {
22             dfz(v, u), sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v;
23     }
24 }
25 void add(int u, int f) {
26     if (++cnt[c[u]] > mx) mx = cnt[c[u]], tans = c[u];
27     else if (cnt[c[u]] == mx) tans += c[u];
28     for (int v, i = 0; i < g[u].size(); ++i)
29         if ((v = g[u][i]) != f && !h[v]) add(v, u);
30 }
31 void del(int u, int f) {
32     --cnt[c[u]];
33     for (int v, i = 0; i < g[u].size(); ++i) 
34         if ((v = g[u][i]) != f && !h[v]) del(v, u);
35 }
36 void dfs(int u, int f, bool k) {
37     int s = son[u];
38     for (int v, i = 0; i < g[u].size(); ++i)
39         if ((v = g[u][i]) != f && v != s) dfs(v, u, 0);
40     if (s) dfs(s, u, 1), h[s] = 1;
41     add(u, f); ans[u] = tans;
42     if (s) h[s] = 0;
43     if (!k) del(u, f), mx = tans = 0;
44 }
45 int main() {
46     read(n);
47     for (int i = 1; i <= n; ++i) read(c[i]);
48     for (int u, v, i = 1; i < n; ++i)
49         read(u), read(v), aE(u, v);
50     dfz(1, 1); dfs(1, 1, 0);
51     for (int i = 1; i <= n; ++i) printf("%lld ", ans[i]);
52     return 0;
53 }
View Code

把小的往大的上面合并,有丝按秩合并并查集的味道。

CodeForces - 570D

 1 #include<vector>
 2 #include<cstdio>
 3 #include<bitset>
 4 #include<iostream>
 5 using namespace std;
 6 inline char nc() {
 7     static char b[1<<20],*s=b,*t=b;
 8     return s==t&&(t=(s=b)+fread(b,1,1<<20,stdin),s==t)?-1:*s++;
 9 }
10 inline void read(int &x) {
11     char b = nc(); x = 0;
12     for (; !isdigit(b); b = nc());
13     for (; isdigit(b); b = nc()) x = x * 10 + b - '0';
14 }
15 inline void read(char &x) {
16     for (x = nc(); !isalpha(x); x = nc());    
17 }
18 const int N = 500010;
19 int n, m, dep[N], sz[N], son[N];
20 char s[N];
21 vector < int > g[N];
22 struct Node {int h, id;};
23 vector < Node > q[N];
24 bool T[N][26];
25 bitset < N > ans, o;
26 void dfs(int u) {
27     sz[u] = 1;
28     for (int i = 0, v; i < g[u].size(); ++i) {
29         v = g[u][i]; dep[v] = dep[u] + 1; dfs(v);
30         sz[u] += sz[v]; if (sz[son[u]] < sz[v]) son[u] = v;
31     }
32 }
33 void acc(int u) {
34     T[dep[u]][s[u]] ^= 1;
35     for (int i = 0; i < g[u].size(); ++i)
36         if (!o[g[u][i]]) acc(g[u][i]);
37 }
38 inline bool check(int d) {
39     int cnt = 0;
40     for (int i = 0; i < 26; ++i)
41         cnt += T[d][i];
42     return cnt <= 1;
43 }
44 void dfs(int u, bool k) {
45     int s = son[u];
46     for (int i = 0; i < g[u].size(); ++i)
47         if (g[u][i] != s) dfs(g[u][i], 0);
48     if (s) dfs(s, 1), o[s] = 1; acc(u);
49     for (int i = 0; i < q[u].size(); ++i)
50         ans[q[u][i].id] = check(q[u][i].h);
51     if (s) o[s] = 0; if (!k) acc(u);
52 }
53 int main() {
54     read(n); read(m); dep[1] = 1;
55     for (int v = 2, u; v <= n; ++v)
56         read(u), g[u].push_back(v);
57     for (int i = 1; i <= n; ++i) read(s[i]), s[i] -= 'a';
58     dfs(1);
59     for (int h, u, i = 0; i < m; ++i) {
60         read(u), read(h); q[u].push_back((Node){h, i});
61     } dfs(1, 0);
62     for (int i = 0; i < m; ++i) puts(ans[i] ? "Yes" : "No");
63     return 0;
64 }
View Code

如果要能构成回文的话,那么出现奇数次字符的个数需小于2。

在查询节点处拉一个链表,dfs递归处理。

现在需要解决的问题就是如何快速回答以x为根的子树中高度为h的字符出现的次数。

很显然可以用可持久化trie树解决这个问题,不过这不是这里的重点。

还是向上面一样,保留重儿子的贡献,轻儿子的贡献再算一遍。

CodeForces - 741D

 1 #include<cstring>
 2 #include<bitset>
 3 #include<vector>
 4 #include<cstdio>
 5 #include<iostream>
 6 #define pb push_back
 7 using namespace std;
 8 inline char nc() {
 9     static char b[1<<14],*s=b,*t=b;
10     return s==t&&(t=(s=b)+fread(b,1,1<<14,stdin),s==t)?-1:*s++;
11 }
12 inline void read(int &x) {
13     char b = nc(); x = 0;
14     for (; !isdigit(b); b = nc());
15     for (; isdigit(b); b = nc()) x = x * 10 + b - '0';
16 }
17 inline void readc(int &b) {
18     for (b = nc(); !isalpha(b); b = nc());
19 }
20 const int N = 500010;
21 int n, dep[N], sz[N], son[N], d[N], f[1<<22], ans[N], inf;
22 vector < int > g[N];
23 bitset < N > o;
24 inline void gmax(int &x, int y) {
25     if (x < y) x = y;
26 }
27 void dfs(int u) {
28     sz[u] = 1;
29     for (int i = 0, v; i < g[u].size(); ++i) {
30         v = g[u][i]; dep[v] = dep[u] + 1; d[v] ^= d[u];
31         dfs(v); sz[u] += sz[v];
32         if (sz[son[u]] < sz[v]) son[u] = v;
33     }
34 }
35 void add(int u) {
36     gmax(f[d[u]], dep[u]);
37     for (int i = 0; i < g[u].size(); ++i)
38         if (!o[g[u][i]]) add(g[u][i]);
39 }
40 void del(int u) {
41     f[d[u]] = inf;
42     for (int i = 0; i < g[u].size(); ++i)
43         if (!o[g[u][i]]) del(g[u][i]);
44 }
45 inline void upd(int u, int &res) {
46     gmax(res, dep[u] + f[d[u]]);
47     for (int i = 0; i < 22; ++i)
48         gmax(res, f[(1<<i)^d[u]] + dep[u]);
49 }
50 void calc(int u, int &res) {
51     upd(u, res); for (int i = 0; i < g[u].size(); ++i)
52         if (!o[g[u][i]]) calc(g[u][i], res);
53 }
54 void dfs(int u, bool k) {
55     int s = son[u], res = 0;
56     for (int i = 0; i < g[u].size(); ++i)
57         if (g[u][i] != s) dfs(g[u][i], 0);
58     if (s) dfs(s, 1), o[s] = 1;
59     for (int v, i = 0; i < g[u].size(); ++i)
60         if ((v = g[u][i]) != s) calc(v, res), add(v);
61     gmax(f[d[u]], dep[u]); upd(u, res);
62     ans[u] = res - 2 * dep[u];
63     for (int i = 0; i < g[u].size(); ++i)
64         gmax(ans[u], ans[g[u][i]]);
65     if (s) o[s] = 0; if (!k) del(u);
66 }
67 void upd(int u) {
68     for (int i = 0; i < g[u].size(); ++i)
69         upd(g[u][i]), gmax(ans[u], ans[g[u][i]]);
70 }
71 int main() {
72     read(n); memset(f, 128, sizeof(f)); inf = f[0];
73     for (int i = 2, b, t; i <= n; ++i)
74         read(t), readc(b), g[t].pb(i), d[i] |= (1 << (b - 'a'));
75     dfs(1); dfs(1, 0);
76     for (int i = 1; i <= n; ++i) printf("%d ", ans[i]);
77     return 0;
78 }
View Code

这道题一开始咱错的原因很搞笑,add和del函数里的add与del我写顺手写成dfs了。

把字符都当成二进制上的某一位。预处理根节点到每一点的异或和$f(1,i)$。

由异或的性质可知,$f(u,v) = f(1,u) \; xor \; f(1,v)$。

所以如果$u-v$这条路径上的字符可以构成回文,那么$f(u,v)$上至多有1位为1,即$f(u,v)=0 \; or \; f(u,v)=1<<i$。

咱只需要在以$x$为根的子树中找出$u,v$满足$f(u,v)=0 \; or \; f(u,v)=1<<i$,且使得$dep[u]+dep[v]-2*dep[lca(u,v)]$最大。

但是$lca(u,v)$不一定是$x$,这就会造成咱用$dep[u]+dep[v]-2*dep[x]$所计算出的答案大了。

所以咱要操作一番,让$u,v$在$x$的不同子树中。

这个好搞哟,咱只要先统计一棵子树再去更新它就行。

ref:

树上启发式合并 (dsu on tree)

[trick]dsu on tree

【学习笔记】dsu on tree

[dsu on tree]【学习笔记】

白金ディスコ

转载于:https://www.cnblogs.com/p0ny/p/8111236.html

#include <bits/stdc++.h> using namespace std; typedef long long ll; #define rep(i,s,t) for(register ll i = s;i <= t;++i) #define per(i,t,s) for(register ll i = t;i >= s;--i) const ll N = 1e6 + 5; ll n; ll k; ll rt1; ll rt2; ll top; ll idx; ll ans; ll p[N] = {}; ll q[N] = {}; ll fa[N] = {}; ll st[N] = {}; ll sz[N] = {}; ll siz[N] = {}; ll dfn[N] = {}; ll son[N] = {}; vector<ll> g[N]; vector<ll> g1[N]; vector<ll> g2[N]; class binary_indexed_tree { private: ll t[N] = {}; public: inline void init() { memset(t,0,sizeof(t)); } inline ll lowbit(ll x) { return x & (-x); } inline void upd(ll x,ll k) { while(x <= n) { t[x] += k; x += lowbit(x); } } inline ll qry(ll x) { ll ans = 0; while(x) { ans += t[x]; x -= lowbit(x); } return ans; } }; binary_indexed_tree t1; binary_indexed_tree t2; inline ll read() { ll x = 0; ll y = 1; char c = getchar(); while(c < '0' || c > '9') { if(c == '-') y = -y; c = getchar(); } while(c >= '0' && c <= '9') { x = (x << 3) + (x << 1) + (c ^ '0'); c = getchar(); } return x * y; } inline void write(ll x) { if(x < 0) { putchar('-'); write(-x); return; } if(x > 9) write(x / 10); putchar(x % 10 + '0'); } inline void dfs(ll u) { siz[u] = 1; dfn[u] = ++idx; for(register auto v : g1[u]) { dfs(v); siz[u] += siz[v]; } } inline void dfs1(ll u) { st[++top] = u; g[u].clear(); if(top > k) fa[u] = st[top - k]; else fa[u] = 0; if(fa[u]) g[fa[u]].push_back(u); sz[u] = 1; son[u] = 0; for(auto v : g2[u]) { dfs1(v); if(sz[v] > sz[son[u]]) son[u] = v; sz[u] += sz[v]; } top--; } inline void ins(ll x,ll k) { t1.upd(dfn[x],k); t1.upd(dfn[x] + siz[x],-k); t2.upd(dfn[x],k); } inline ll query(ll x) { return t1.qry(dfn[x]) + t2.qry(dfn[x] + siz[x] - 1) - t2.qry(dfn[x] - 1); } inline void dfs3(ll u,ll k) { for(auto v : g[u]) { if(k == 1) ans += query(v); else if(k == 2) ins(v,1); else if(k == 3) ins(v,-1); } for(auto v : g2[u]) dfs3(v,k); } inline void dfs2(ll u,ll k) // k is keep flag { for(auto v: g2[u]) if(v != son[u]) dfs2(v,0); if(son[u]) dfs2(son[u],1); for(auto v: g2[u]) if(v != son[u]) { dfs3(v,1); dfs3(v,2); } for(register auto v : g[u]) ins(v,1); if(!k) dfs3(u,3); } // 添加重置全局状态的函数 inline void reset_global() { // 重置 DFS 相关全局状态 top = 0; idx = 0; // 重置树状数组在 cal 逻辑中处理,不在此重置 // 重置 DSU 相关数组 memset(sz, 0, sizeof(sz)); memset(son, 0, sizeof(son)); memset(fa, 0, sizeof(fa)); memset(st, 0, sizeof(st)); // g 数组在 dfs1 中每个节点清空,无需全局重置 } // 封装 cal 函数,第一段代码一致 inline void cal() { reset_global(); // 重置全局状态 dfs(rt1); // 在 T1 上 DFS t1.init(); // 清空树状数组 t2.init(); top = 0; dfs1(rt2); // 在 T2 上处理第 k 祖先 dfs2(rt2, 0); // DSU on tree } int main() { freopen("D.in","r",stdin); freopen("D.out","w",stdout); n = read(); k = read(); rep(i,1,n) p[i] = read(); rep(i,1,n) q[i] = read(); rep(i,1,n) { if(!p[i]) rt1 = i; else g1[p[i]].push_back(i); if(!q[i]) rt2 = i; else g2[q[i]].push_back(i); } // 第一次计算 cal(); // 交换树 rep(i,1,n) { swap(p[i],q[i]); swap(g1[i],g2[i]); } swap(rt1,rt2); // 第二次计算 cal(); write(ans); fclose(stdin); fclose(stdout); return 0; }小丁的树 题目描述 小丁拥有两棵均具有 n n 个顶点,编号集合为 { 1 , 2 , ⋯   , n } {1,2,⋯,n} 的有根树 T 1 , T 2 T 1 ​ ,T 2 ​ ,现在他需要计算这两棵树的相似程度。 为了计算,小丁定义了对于一棵树 T T 和 T T 上两个不同顶点 u , v u,v 的距离函数 d T ( u , v ) d T ​ (u,v),其定义为 u , v u,v 两个点距离成为祖先关系有多近,具体来说,对于所有在 T T 上为祖先关系的点对 ( u ′ , v ′ ) (u ′ ,v ′ ), dis ⁡ ( u , u ′ ) + dis ⁡ ( v , v ′ ) dis(u,u ′ )+dis(v,v ′ ) 的最小值即为 d T ( u , v ) d T ​ (u,v) 的值,其中 dis ⁡ ( u , v ) dis(u,v) 表示 u , v u,v 在树 T T 上的唯一简单路径包含的边数,即 u , v u,v 的距离。 点对 ( u ′ , v ′ ) (u ′ ,v ′ ) 为祖先关系,当且仅当 u ′ u ′ 是 v ′ v ′ 的祖先或 v ′ v ′ 是 u ′ u ′ 的祖先。(注意,每个点都是自己的祖先) 小丁心里还有一个参数 k k,如果节点对 ( u , v ) (u,v) 满足以下条件,称之为不相似的节点对: 1 ≤ u < v ≤ n 1≤u<v≤n " d T 1 ( u , v ) = 0 d T 1 ​ ​ (u,v)=0 且 d T 2 ( u , v ) > k d T 2 ​ ​ (u,v)>k“ 或 " d T 2 ( u , v ) = 0 d T 2 ​ ​ (u,v)=0 且 d T 1 ( u , v ) > k d T 1 ​ ​ (u,v)>k​“ 小丁认为,不相似的节点对越多, T 1 T 1 ​ 和 T 2 T 2 ​ 就越不相似,你能告诉他总共有多少不相似的节点对吗? 输入格式 第一行两个整数 n , k n,k,表示 T 1 T 1 ​ 和 T 2 T 2 ​ 的节点数和参数 k k。 第二行 n n 个正整数 p 1 , p 2 , ⋯   , p n p 1 ​ ,p 2 ​ ,⋯,p n ​ , T 1 T 1 ​ 中节点 i i 的父节点为 p i p i ​ ,特别的,若 p i = 0 p i ​ =0,则 i i 是 T 1 T 1 ​ 的根。 第三行 n n 个正整数 q 1 , q 2 , ⋯   , q n q 1 ​ ,q 2 ​ ,⋯,q n ​ , T 2 T 2 ​ 中节点 i i 的父节点为 q i q i ​ ,特别的,若 q i = 0 q i ​ =0,则 i i 是 T 2 T 2 ​ 的根。 输出格式 一行一个整数,表示不相似的节点对总数。 样例 1 输入 5 0 0 1 1 2 3 5 3 1 1 0 样例 1 输出 4 样例 1 解释 ( 2 , 3 ) , ( 2 , 4 ) , ( 2 , 5 ) , ( 4 , 5 ) (2,3),(2,4),(2,5),(4,5) 为不相似的节点对。 其余样例见下发文件。 数据规模约定 对于所有数据, 1 ≤ n ≤ 2 × 10 5 , 0 ≤ k < n , 0 ≤ p i , q i ≤ n 1≤n≤2×10 5 ,0≤k<n,0≤p i ​ ,q i ​ ≤n,且由 p i , q i p i ​ ,q i ​ 形成的是一棵 n n 个节点的有根树。 本题采用捆绑评测,你只有通过了一个子任务中所有测试点才能得到该子任务的分数。 Subtask 1(10pts): 1 ≤ n ≤ 100 1≤n≤100。 Subtask 2(20pts): 1 ≤ n ≤ 3000 1≤n≤3000。 Subtask 3(20pts): k = 0 k=0。 Subtask 4(10pts): 0 ≤ k ≤ 20 0≤k≤20。 Subtask 5(40pts):无特殊限制。 请详细注释上述代码的每一行,给出详细的解题思路,并完整按照代码流程模拟样例
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值