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/ 希望所写的文章对您有帮助。
原文地址:https://www.cnblogs.com/qieqiemin/p/13518947.html