dsu on tree 简单总结

什么是dsu on tree:

对于一棵树,如果我们需要统计每一个点的子树的信息,比如求子树中哪个点权出现的次数最多(最大值这种就是树剖裸题了搞什么$dsu$ $on$ $tree$?)这样子,如果我们对于每一个子树都进行遍历的话,时间复杂度将达到$O(n^2)$,当$n \leq 1e5$的时候,这个时间复杂度无法接受。那么我们应该怎么办呢?这个时候就需要使用$dsu$ $on$ $tree$。

我们知道,树链剖分可以把一棵树分成$O(log_2 n)$条重链,剩下的都是轻边。现在证明一下从根到某结点经过的轻边的数量:因为每经过一条轻边,结点数是一半,所以$size \leq \frac{n}{2^{edge}}$,又$n > 2^{edge}$,所以$edge<log_2 n$。即只会经过$O(log_2 n)$条轻边。

我们先从上到下遍历结点,然后对于某个节点,我们访问所有的轻儿子,然后求得了这些轻儿子的子树信息,接下来我们考虑重儿子。访问重儿子,然后保留重儿子的信息,对于轻儿子就暴力处理。我们分析它的时间复杂度:我们假设这棵子树的大小是$n$。则我们暴力统计所有轻儿子的时间复杂度是$O(log_2 n)$,保留重儿子信息,即重儿子信息$O(1)$上推,则我们就可以$O(log_2 n)$求出这棵子树的信息。

当然,树不能有修改,不然重儿子的信息就会失效。

并且,所有询问都针对子树。

其思想是启发式合并,因为我还没有学,所以先这样,学了再回来总结。

dsu on tree实现:

算法流程:

一、轻重链剖分

然后这个过程不好理解,我给出路径:

进入轻儿子直到叶节点,处理这个叶节点的信息,然后更新这个轻儿子答案,并删除轻儿子的信息,然后回溯到它的父亲,然后进入重儿子,假设这个重儿子是叶节点,我们处理重儿子的信息,保留信息并更新答案,然后回溯到它的父亲,它的父亲将会暴力统计这个轻儿子的信息,然后更新自己(因为重儿子信息没有删除),然后删除轻儿子的信息,这个节点处理完,回溯到上一层,这样子我们就保留了重儿子的信息以处理这个子树根结点的父亲。显然,因为本身我们进入的是一个轻儿子,所以在处理完它的父节点时,这个轻儿子的信息会被删除(这个建议画图理解,非常绕)。

例题:

1、codeforces600E Lomsat gelral

题意:

给出一棵树,求出每个节点的子树中出现次数最多的颜色的编号和。

题解:

直接$dsu$ $on$ $tree$,用桶记录颜色次数即可。

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
vector<int> G[MAXN];
int col[MAXN], son[MAXN], sz[MAXN];
bool vis[MAXN];
ll tot[MAXN], ans[MAXN];
ll maxn, c;
void dfs1(int u, int fa)
{
    sz[u] = 1;
    for (auto i : G[u])
        if (i != fa)
        {
            dfs1(i, u);
            sz[u] += sz[i];
            if (sz[i] > sz[son[u]])
                son[u] = i;
        }
}
void update(int u, int fa, int p)
{
    tot[col[u]] += p;
    if (p > 0 && maxn == tot[col[u]])
        c += col[u];
    else if (p > 0 && maxn < tot[col[u]])
        c = col[u], maxn = tot[col[u]];
    for (auto i : G[u])
        if (i != fa && !vis[i])
            update(i, u, p);
}
void dfs2(int u, int fa, int op)
{
    for (auto i : G[u])
        if (i != son[u] && i != fa)
            dfs2(i, u, 0); 
    if (son[u])
        dfs2(son[u], u, 1), vis[son[u]] = 1;
    update(u, fa, 1), ans[u] = c;
    if (son[u])
        vis[son[u]] = 0;
    if (!op)
        update(u, fa, -1), maxn = c = 0;
}
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &col[i]);
    int a, b;
    for (int i = 1; i < n; ++i)
    {
        scanf("%d%d", &a, &b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    dfs1(1, 0);       //tot: n
    dfs2(1, 0, 0); //tot: n+2nlogn
    for (int i = 1; i <= n; ++i)
        printf("%lld%c", ans[i], i == n ? '\n' : ' ');
    return 0;
}
View Code

注:为什么$update$之后$vis[son[u]]$要置零,这是因为,只要$op$是$0$,这个对于其父亲来说轻儿子,所以实际上执行到这里的时候,它的父亲的重儿子的信息处理好了,所以就要把这个轻儿子的所有信息全部删去,这个很绕。

2、codeforces570D Tree Requests

题意:

给出一棵树,树上每个节点上有一个小写字符,给出一些询问,询问包括节点和深度,求出以这个节点为根,固定深度的节点上能否组成一个回文串。

题解:

看满不满足使用条件:无修改,查询子树。满足条件,然后我们看维护什么,维护一个回文串,这个其实不好做,显然我们不能把这些深度的字符串都保存下来,因为这将达到$O(n^2)$的空间,所以我们只能用更加省空间的方案。一个字符串构成回文串,里面的同种字符的数量至多出现一个奇数,所以我们实际关注的是,这些字符串模$2$的和是不是小于$2$,因为模$2$就是二进制,所以我们直接用一个整数维护这个字符串,用异或增减字符即可。

为什么我们这里可以处理相对某个结点深度大于$1$的结点呢?这是因为处理完重儿子之后到删除轻儿子信息之前,这棵子树的任何一个结点除了它自己的信息都是知道的,因为重儿子信息被保存,轻儿子子树的所有结点信息被处理出来,所以就可以把这个子树为根的询问全部回答了。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
vector<int> G[MAXN];
struct Q
{
    int pos, dep;
    Q(int _pos, int _dep) : pos(_pos), dep(_dep) {}
};
vector<Q> q[MAXN];
bool ans[MAXN];
int dep[MAXN], val[MAXN], sz[MAXN];
int tot[MAXN], son[MAXN];
bool vis[MAXN];
void dfs1(int u, int fa)
{
    dep[u] = dep[fa] + 1;
    sz[u] = 1;
    for (auto i : G[u])
        if (i != fa)
        {
            dfs1(i, u);
            sz[u] += sz[i];
            if (sz[i] > sz[son[u]])
                son[u] = i;
        }
}
bool get1(int val)
{
    int rt = 0;
    while (val)
        rt += val % 2, val >>= 1;
    return rt < 2;
}
void update(int u, int fa)
{
    tot[dep[u]] ^= val[u];
    for (auto i : G[u])
        if (i != fa && !vis[i])
            update(i, u);
}
void del(int u, int fa)
{
    tot[dep[u]] = 0;
    for (auto i : G[u])
        if (i != fa && !vis[i])
            del(i, u);
}
void dfs2(int u, int fa, int op)
{
    for (auto i : G[u])
        if (i != fa && i != son[u])
            dfs2(i, u, 0);
    if (son[u])
        dfs2(son[u], u, 1), vis[son[u]] = 1;
    update(u, fa);
    for (int i = 0; i < q[u].size(); ++i)
        ans[q[u][i].pos] = get1(tot[q[u][i].dep]);
    if (son[u])
        vis[son[u]] = 0;
    if (!op)
        del(u, fa);
}
char str[MAXN];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    int a;
    for (int i = 1; i < n; ++i)
    {
        scanf("%d", &a);
        G[i + 1].push_back(a);
        G[a].push_back(i + 1);
    }
    scanf("%s", str + 1);
    for (int i = 1; i <= n; ++i)
        val[i] = 1 << (str[i] - 'a');
    int x, y;
    for (int i = 1; i <= m; ++i)
    {
        scanf("%d%d", &x, &y);
        q[x].push_back(Q(i, y));
    }
    dfs1(1, 0);
    dfs2(1, 0, 0);
    for (int i = 1; i <= m; ++i)
        printf(ans[i] ? "Yes\n" : "No\n");
    return 0;
}
View Code

3、codeforces246E

($To$ $be$ $continued$)博主先去做了

猜你喜欢

转载自www.cnblogs.com/Aya-Uchida/p/12716998.html