dsu on tree

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 }

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

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 }

如果要能构成回文的话,那么出现奇数次字符的个数需小于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 }

这道题一开始咱错的原因很搞笑,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或1<<i。

咱只需要在以x为根的子树中找出u,v满足f(u,v)==0或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:

[Tutorial] Sack (dsu on tree)

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

[trick]dsu on tree

【学习笔记】dsu on tree

[dsu on tree]【学习笔记】

白金ディスコ

原文地址:https://www.cnblogs.com/p0ny/p/8111236.html

时间: 2024-10-26 01:36:38

dsu on tree的相关文章

Codeforces 600E. Lomsat gelral(Dsu on tree学习)

题目链接:http://codeforces.com/problemset/problem/600/E n个点的有根树,以1为根,每个点有一种颜色.我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多.求占领每个子树的所有颜色之和. 我们都知道可以$BST$启发式合并从而完美${O(nlogn^{2})}$,这太丑陋了. 那么$Dsu~~on~~tree$是在干啥呢? 找出树中每一个节点的重儿子,统计答案的时候优先进入每一个点的所有轻儿子,之后再进入重儿子,目的是保留重儿子所

dsu on tree总结

dsu on tree 树上启发式合并.我并不知道为什么要叫做这个名字... 干什么的 可以在\(O(n\log n)\)的时间内完成对子树信息的询问,可横向对比把树按\(dfs\)序转成序列问题的\(O(n\sqrt n)\)莫队算法. 怎么实现 当\(dfs\)到一个点\(u\),执行以下操作: 1.递归处理所有轻儿子; 2.递归处理重儿子; 3.计算整棵子树的贡献(在第2步中重儿子的贡献得以保留,所以不需要重复计算); 4.若点\(u\)不是其父亲的重儿子,删除整棵子树的贡献. 看上去像是

图论-树上启发式合并(DSU On Tree)

Disjoint Set Union On Tree ,似乎是来自 Codeforces 的一种新操作,似乎被叫做"树上启发式合并". 在不带修改的有根树子树信息统计问题中,似乎树上莫队和这个 DSU On Tree 是两类常规操作. 先对树按轻重链剖分.对于每个节点,先计算轻儿子为根的子树信息,每次计算后消除影响,再去计算其他轻儿子.然后计算重儿子为根的子树信息,不消除影响,并把轻儿子们为根的子树信息加入,再合并这个节点本身的信息.由于一个大小 \(x\) 的子树被消除影响后,都把信

DSU on tree浅谈

DSU on tree 在之前的一次比赛中,学长向我们讲了了这样一个神奇的思想:DSU on tree(树上启发式合并),看上去就非常厉害--但实际上是非常暴力的一种做法;不过暴力只是看上去暴力,它在处理不带修改的子树统计问题时有着优秀的时间复杂度\(O(Nlog N)\),显然在处理这一类问题上,它是优于我们常用的\(dfs\)序后莫队,更关键是它十分好写. 算法实现: 首先对所有轻儿子的子树信息进行统计,然后暴力擦除所有轻儿子的影响.再统计重儿子为根的子树信息,并将轻儿子的信息合并起来,加上

树上统计treecnt(dsu on tree 并查集 正难则反)

题目链接 \(Description\) 给定一棵\(n(n\leq 10^5)\)个点的树. 定义\(Tree[L,R]\)表示为了使得\(L\sim R\)号点两两连通,最少需要选择的边的数量. 求\[\sum_{l=1}^n\sum_{r=l}^nTree[l,r]\] \(Solution\) 枚举每条边,计算它的贡献. 那么我们要判断有多少连续区间的点跨过这条边,并不好算,反过来去求在这条边的两侧分别有多少个连续区间. 那么显然有\(O(n^2)\)的做法,即对每条边DFS它的两侧,枚

Codeforces.600E.Lomsat gelral(dsu on tree)

题目链接 dsu on tree参见这. \(Description\) 给定一棵树.求以每个点为根的子树中,出现次数最多的颜色的和. \(Solution\) dsu on tree模板题. 用sum[i]表示出现次数为i的颜色的和,cnt[i]表示出现次数为i的颜色有多少个(其实有个Max表示当前最多的次数,和tm[i]就好了),然后就这样了.. 再写一遍dsu on tree大体过程:(设当前点为x) 计算轻儿子子树的答案,并删掉轻儿子的贡献(大多数时候): 计算重儿子子树的答案,保留重儿

Codeforces.741D.Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on tree 思路)

题目链接 \(Description\) 给定一棵树,每条边上有一个字符(a~v).对每个节点,求它的子树中一条最长的路径,满足 路径上所有边上的字符可以重新排列成一个回文串.输出其最长长度. \(n\leq 5\times10^5\). \(Solution\) 可以构成回文串,即要么所有字符都出现了偶数次,要么有一个出现了奇数次.其余都出现了偶数次. 转化为异或!把每个字符c(0~21)映射到1<<c上去. 令\(s[x]\)表示根节点到\(x\)路径上边权的异或和.那么路径\((u,v)

CF 600E. Lomsat gelral(dsu on tree)

解题思路 \(dsu\) \(on\) \(tree\)的模板题.暴力而优雅的算法,轻儿子的信息暴力清空,重儿子的信息保留,时间复杂度\(O(nlogn)\) 代码 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<set> using namespace std; const int

Codeforces 600E - Lomsat gelral 「$Dsu \ on \ tree$模板」

With $Dsu \ on \ tree$ we can answer queries of this type: How many vertices in the subtree of vertex $v$ has some property in $O (n \log n)$ time (for all of the queries)? 这题写的是轻重儿子(重链剖分)版本的 $Dsu \ on \ tree$ 具体流程如下: 每次先递归计算轻儿子,再单独递归重儿子,计算完后轻儿子的一些信息