dsu on tree
Posted p0ny^v^
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dsu on tree相关的知识,希望对你有一定的参考价值。
osu on tree?
dsu on tree!
这种操作可以在$O(nlogn)$的时间内解决一些无修改子树询问问题。
咱知道把一棵树轻重链剖分后,树的轻链,重链都只有$O(logn)$个。
这个算法就是利用了这一点,递归处理时保留重儿子的信息,轻儿子的则重新计算。
乍一看感觉很暴力,但是实际上是$O(nlogn)$的。
来看几道题吧。
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 }
把小的往大的上面合并,有丝按秩合并并查集的味道。
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 }
如果要能构成回文的话,那么出现奇数次字符的个数需小于2。
在查询节点处拉一个链表,dfs递归处理。
现在需要解决的问题就是如何快速回答以x为根的子树中高度为h的字符出现的次数。
很显然可以用可持久化trie树解决这个问题,不过这不是这里的重点。
还是向上面一样,保留重儿子的贡献,轻儿子的贡献再算一遍。
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 }
这道题一开始咱错的原因很搞笑,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的主要内容,如果未能解决你的问题,请参考以下文章