zoukankan      html  css  js  c++  java
  • Namomo Cockfight Round 5-D String (01字典树,tire)

    Namomo Cockfight Round 5-D String (01字典树,tire)

    题面:

    思路:

    很显然是一个字典树的题,且只有0,1字符。

    我们对于字典树每一个节点的开以下信息:

    (tree[i][0 or 1]) 代表第i 个节点的0 or 1儿子节点的编号。

    (e[i])代表在第(mathit i) 个节点结束的所有字符串的总价值。

    (pass[i])代表经过第(mathit i) 个节点的所有字符串的总价值。

    (flagg[i]) 代表是否有在第(mathit i) 个节点结束的字符串,(因为本题目所有字符串的价值大于( ext 0) ,所以这个数组其实可以用([e[i]>0]) 代替,我就直接偷懒不改了。)

    插入,查询,删除都是字典树的常规,我这里就不做详述了,

    重点讲一下第四个操作,即更换字符串前缀。

    我们先找到(rt2)代表以字符串(mathit s) 为前缀的子树根节点编号。

    (rt1)代表以字符串(mathit t) 为前缀的子树根节点编号。

    显然(pass[rt2]) 是所有以字符串(mathit s) 为前缀的字符串价值总和。

    我们应该对(root->rt2)路径中每一个节点(mathit i)(pass[i]) 减掉(pass[rt2])。(root是总树根)

    也要对(root->rt1)路径中每一个节点(mathit j)(pass[j]) 加上(pass[rt2])

    然后将(rt2) 为根的子树合并到(rt1)为根的子树中。

    合并过程如下:

    (rt2)为根的子树叫右树,(rt1)为根的左树

    对于每一个递归到的节点,将其有右树中的节点信息((pass[j],e[j],flagg[j])等等)加到左树中。

    如果左树中的一个节点没有(0/1)儿子,而右树对应对着的节点有该儿子,那么直接将该儿子与右树断开,连在左数上对应位置即可。

    可以看下下图理解一下:

    很多人认为该部分操作是(O(n))的,实则不是,

    考虑均摊时间,每一次你合并了(num)个节点,时间复杂度是(O(num))的,但是整个树形中节点就会减少(num)个,即每一个节点只会被删除一次,所以读入中的每一个字符的贡献都是一个常数。

    这题的总时间复杂度就为(O(sum |str|))

    官方题解为:

    本题在trie上维护包含某个前缀的串的值的和,直接暴力即可。下面介绍如何分析均摊复杂度,即证明每个输入的字符只会产生常数的运行时间。插入字符串时,从trie的根开始,每个字符会往下走一步,如果没有对应的点会新建一个点。走到最下面把维护的值加上这次插入的串的值即可。这样每个字符只消耗常数时间(走一步和新建一个点)。删除类似,走到字符串对应的点后,用该点维护的值减掉它两个子树维护的值,就可以得到所有字符串s的值的和,称为d。再从这个点走回根,沿途将维护的值减少d即可。询问也类似,走到对应的点输出维护的值。最后一种修改操作,由于s和t公共前缀为空,所以代表sx的串在trie中对应的子树和代表tx的串对应的子树无交。我们找到两棵子树的根(称为s和t),递归的合并他们。首先把s维护的值加在t维护的值上,t作为合并后的根,s可以删除。对于s和t各自的左儿子(字符为0的儿子),如果至少一个儿子不存在,把存在的那个儿子作为合并后的左儿子即可。如果两个左儿子都存在,递归合并这两个左儿子。右儿子同理。这样,每次要么递归不继续进行,要么在递归的同时trie里面点的数量会减一(两个根合并后只剩一个)。由于trie里每个点都是某个输入的字符创建的,所以每次递归的复杂度平摊在每个输入的字符上只有常数。

    代码:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <bits/stdc++.h>
    #define ALL(x) (x).begin(), (x).end()
    #define sz(a) int(a.size())
    #define rep(i,x,n) for(int i=x;i<n;i++)
    #define repd(i,x,n) for(int i=x;i<=n;i++)
    #define pii pair<int,int>
    #define pll pair<long long ,long long>
    #define gbtb ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    #define MS0(X) memset((X), 0, sizeof((X)))
    #define MSC0(X) memset((X), '', sizeof((X)))
    #define pb push_back
    #define mp make_pair
    #define fi first
    #define se second
    #define eps 1e-6
    #define chu(x)  if(DEBUG_Switch) cout<<"["<<#x<<" "<<(x)<<"]"<<endl
    #define du3(a,b,c) scanf("%d %d %d",&(a),&(b),&(c))
    #define du2(a,b) scanf("%d %d",&(a),&(b))
    #define du1(a) scanf("%d",&(a));
    using namespace std;
    typedef long long ll;
    ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
    ll lcm(ll a, ll b) {return a / gcd(a, b) * b;}
    ll powmod(ll a, ll b, ll MOD) { if (a == 0ll) {return 0ll;} a %= MOD; ll ans = 1; while (b) {if (b & 1) {ans = ans * a % MOD;} a = a * a % MOD; b >>= 1;} return ans;}
    ll poww(ll a, ll b) { if (a == 0ll) {return 0ll;} ll ans = 1; while (b) {if (b & 1) {ans = ans * a ;} a = a * a ; b >>= 1;} return ans;}
    void Pv(const vector<int> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%d", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("
    ");}}}
    void Pvl(const vector<ll> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%lld", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("
    ");}}}
    inline long long readll() {long long tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
    inline int readint() {int tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
    void pvarr_int(int *arr, int n, int strat = 1) {if (strat == 0) {n--;} repd(i, strat, n) {printf("%d%c", arr[i], i == n ? '
    ' : ' ');}}
    void pvarr_LL(ll *arr, int n, int strat = 1) {if (strat == 0) {n--;} repd(i, strat, n) {printf("%lld%c", arr[i], i == n ? '
    ' : ' ');}}
    // const int maxn = 1000010;
    const int inf = 0x3f3f3f3f;
    /*** TEMPLATE CODE * * STARTS HERE ***/
    #define DEBUG_Switch 0
    const int maxn = 2e6 + 5; //如果是64MB可以开到2e6+5,尽量开大
    int tree[maxn][2];//tree[i][j]表示节点i的第j个儿子的节点编号
    ll e[maxn], pass[maxn];
    bool flagg[maxn];//表示以该节点结尾是一个单词
    int tot;//总节点数
    void insert_(char *str, ll val)
    {
        int len = strlen(str);
        int root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            if (!tree[root][id])
                tree[root][id] = ++tot;
            root = tree[root][id];
            pass[root] += val;
        }
        e[root] += val;
        flagg[root] = true;
    }
    ll search_(char *str)// 查询以str为前缀的字符串数量(val_sum)。
    {
        int len = strlen(str);
        int root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            if (!tree[root][id])
                return 0;
            root = tree[root][id];
        }
        return pass[root];
    }
    bool del(char *str)
    {
        int len = strlen(str);
        int root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            if (!tree[root][id])
                return 0;
            root = tree[root][id];
        }
        if (flagg[root] == 0 || pass[root] == 0)
            return 0;
        ll val = e[root];
        root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            root = tree[root][id];
            pass[root] -= val;
        }
        e[root] = 0;
        flagg[root] = 0;
        return 1;
    }
    void init()//最后清空,节省时间
    {
        for (int i = 0; i <= tot; i++)
        {
            flagg[i] = false;
            pass[i] = 0;
            e[i] = 0;
            for (int j = 0; j < 2; j++)
                tree[i][j] = 0;
        }
        tot = 0; //RE有可能是这里的问题
    }
    int search_root(char *str, ll &val) // 查询以str为前缀的树根。,val=以str为前缀的串总值。
    {
        int len = strlen(str);
        int root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            if (!tree[root][id])
                return 0;
            root = tree[root][id];
        }
        val = pass[root];
        if (val == 0)// 已经被删除
            return 0;
        return root;
    }
    void break_root(int root)// 断掉该根的子树信息。
    {
        tree[root][0] = tree[root][1] = 0;
        flagg[root] = 0;
        pass[root] = e[root] = 0;
    }
    int get_root(char *str, ll val) // 返回以str为前缀的字符串子树根,并将val值加到树根到子树根这个路径中
    {
        int len = strlen(str);
        int root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            if (!tree[root][id])
                tree[root][id] = ++tot;
            root = tree[root][id];
            pass[root] += val;
        }
        return root;
    }
    void merge__(int rt1, int rt2)// 合并 分别以rt1,rt2为根的子树(将rt2的信息挂在rt1上)
    {
        pass[rt1] += pass[rt2];
        e[rt1] += e[rt2];
        flagg[rt1] += flagg[rt2];
    
        pass[rt2] = e[rt2] = flagg[rt2] = 0;
    
        repd(i, 0, 1)
        {
            if (tree[rt2][i] != 0)
            {
                if (tree[rt1][i] == 0)
                {
                    tree[rt1][i] = tree[rt2][i];
                } else
                {
                    merge__(tree[rt1][i], tree[rt2][i]);
                }
            }
        }
    }
    void del_pre(char *str, ll &val) // 删除以str为前缀的字符串的值。
    {
        int len = strlen(str);
        int root = 0;
        for (int i = 0; i < len; i++)
        {
            int id = str[i] - '0';
            root = tree[root][id];
            pass[root] -= val;
        }
    }
    char s[maxn];
    char ct[maxn];
    int main()
    {
    #if DEBUG_Switch
        freopen("C:\code\input.txt", "r", stdin);
    #endif
        //freopen("C:\code\output.txt","w",stdout);
        int t;
        t = readint();
        char op[10];
        for (int cas = 1; cas <= t; ++cas)
        {
            printf("Case %d
    ", cas );
            int q = readint();
            // i str val -> insert a string with it's value
            // d str     -> delete all str strings  
            // q str     -> get the sum of the values of all strings prefixed by str
            // u s t    对于所有形式为 sx 的字符串, 把他们修改成 tx, 其中 x 是任意字符串,可以为空。
            //          如果不存在这样的字符串,输出 "Not Exist"。数据保证 s 和 t 的最长公共前缀为空。
            while (q--)
            {
                scanf("%s", op);
                if (op[0] == 'I')
                {
                    scanf("%s", s);
                    ll val;
                    scanf("%lld", &val);
                    insert_(s, val);
                } else if (op[0] == 'D')
                {
                    scanf("%s", s);
                    bool res = del(s);
                    if (res == 0)
                    {
                        printf("Not Exist
    ");
                    }
                } else if (op[0] == 'Q')
                {
                    scanf("%s", s);
                    ll res = search_(s);
                    printf("%lld
    ", res );
                } else
                {
                    scanf("%s", s);
                    scanf("%s", ct);
                    ll w;
                    int rt2 = search_root(s, w);
                    if (rt2 == 0)
                    {
                        printf("Not Exist
    ");
                    } else
                    {
                        int rt1 = get_root(ct, w);
                        pass[rt1] -= pass[rt2];// pass[rt1] 在get_root,merge_被加了2次,这里需要减去一次。
                        merge__(rt1, rt2);
                        del_pre(s, w);
                        break_root(rt2);
                    }
                }
            }
            init();
        }
        return 0;
    }
    
    
    本博客为本人原创,如需转载,请必须声明博客的源地址。 本人博客地址为:www.cnblogs.com/qieqiemin/ 希望所写的文章对您有帮助。
  • 相关阅读:
    computed计算属性依赖的响应式属性为对象obj时,对象obj中有属性字段【A、B、C】时,对A、B进行了if判断,那么只有A、B变化时,计算属性才更新,否则不更新
    computed计算属性依赖的响应式属性为对象时,只要依赖的属性变化(值同地址不同:变化),即使前后值相同,监听computed计算属性也是在变化
    S7-200SMART与S7-1500通信
    STM32F103+OLED绘制曲线
    两台S7-300PLC之间的PROFIBUS-DP主从通信示例
    VBScript连接mysql数据库
    mysql数据库基础知识--入门必看
    小白版windows下mysql配置
    mysql5.7.12直接解压安装过程
    西门子S7-200SMART 指针
  • 原文地址:https://www.cnblogs.com/qieqiemin/p/13518947.html
Copyright © 2011-2022 走看看