力扣2003 三种解法 启发式合并线段树维护mex运算思维

Posted hesorchen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣2003 三种解法 启发式合并线段树维护mex运算思维相关的知识,希望对你有一定的参考价值。

题目链接

2003. 每棵子树内缺失的最小基因值

给出一颗有根树,每个节点有一个权值 w i w_i wi,求出所有子树的mex值,一个子树的mex值定义为在该子树中最小的未出现的正整数。

题解

Solution I

启发式合并

考虑暴力维护每颗子树包含的数据,当前子树包含的正整数就是所有儿子包含的正整数加上根,并且根据mex运算性质,父亲结点的mex值肯定大于等于儿子结点。

但是暴力合并的时空复杂度都是 O ( n 2 ) O(n^2) O(n2)。可以在合并的时候每次都将小的集合往大的集合合并,这样时间复杂度最坏为 O ( n l o g n ) O(nlogn) O(nlogn)。(当树为满二叉树的时候)

代码:

class Solution
{
public:
    vector<int> mp[400005];
    set<int> st[100005];
    vector<int> mex;
    void dfs(int u, int fa)
    {
        for (auto v : mp[u])
        {
            if (v == fa)
                continue;
            dfs(v, u);
            if (st[u].size() < st[v].size())
                swap(st[u], st[v]);
            for (auto it : st[v])
                st[u].insert(it);
            mex[u] = max(mex[u], mex[v]);
        }
        while (st[u].count(mex[u]))
            ++mex[u];
    }
    vector<int> smallestMissingValueSubtree(vector<int> &parents, vector<int> &nums)
    {
        int n = parents.size();
        mex = vector<int>(n, 1);
        for (int i = 0; i < n; i++)
            st[i].insert(nums[i]);
        for (int i = 0; i < n; i++)
        {
            int fa = parents[i];
            if (fa == -1)
                continue;
            mp[i].emplace_back(fa);
            mp[fa].emplace_back(i);
        }
        dfs(0, -1);
        return mex;
    }
};

Solution II

可以发现,权值为1到根结点这一条链上的答案不为一,其他点的答案都为1。因此可以考虑拿出这一条链暴力维护。有上面mex的性质,时间复杂度为 O ( n ) O(n) O(n)

代码:

class Solution
{
public:
    vector<int> mp[100005];
    vector<int> mex, vis, vis2, NUMS;
    int goal, MEX, N;
    int dfs(int u, int fa)
    {
        bool f = 0;
        for (auto v : mp[u])
        {
            if (v == fa)
                continue;
            if (dfs(v, u))
                f = 1;
        }
        if (u == goal)
            f = 1;
        return vis[u] = f;
    }
    void dfs2(int u, int fa)
    {
        for (auto v : mp[u])
            if (vis[v] && v != fa)
                dfs2(v, u);
        for (auto v : mp[u])
        {
            if (v == fa || vis[v])
                continue;
            dfs2(v, u);
        }
        vis2[NUMS[u]] = 1;
        if (vis[u])
        {
            while (vis2[MEX])
                ++MEX;
            mex[u] = MEX;
        }
    }
    vector<int> smallestMissingValueSubtree(vector<int> &parents, vector<int> &nums)
    {
        int n = parents.size();
        N = n;
        mex = vector<int>(n, 1);
        vis = vector<int>(n + 5, 0);
        goal = -1;
        for (int i = 0; i < n; i++)
            if (nums[i] == 1)
                goal = i;
        if (goal == -1) //不存在1 那么所有子树的mex都是1
            return mex;

        for (int i = 0; i < n; i++)
        {
            int fa = parents[i];
            if (fa == -1)
                continue;
            mp[i].emplace_back(fa);
            mp[fa].emplace_back(i);
        }
        dfs(0, -1); //找到权值为1的点到根结点的路径
        NUMS = nums;
        MEX = 1;
        vis2 = vector<int>(n + 5, 0);
        dfs2(0, -1);
        return mex;
    }
};

Solution III

根据dfs序重新标号。用线段树维护区间mex。代码有空再补。

以上是关于力扣2003 三种解法 启发式合并线段树维护mex运算思维的主要内容,如果未能解决你的问题,请参考以下文章

模板:启发式合并

[CF1083C]Max Mex

P5161 WD与数列(后缀自动机+线段树合并)

P3302 [SDOI2013]森林

cf375D. Tree and Queries(树上启发式合并+线段树)

线段树维护区间合并——cf1285E