首页 > 其他分享 >P6164 后缀平衡树的一种非常规做法

P6164 后缀平衡树的一种非常规做法

时间:2023-12-23 19:56:25浏览次数:44  
标签:非常规 cur 后缀 void P6164 len int str define

【模板】后缀平衡树

Luogu P6164

题目描述

给你一个字符串 init,要求你支持三个操作:

  1. 在当前字符串的后面插入若干个字符。

  2. 在当前字符串的后面删除若干个字符。

  3. 询问字符串 \(s\) 在当前字符串中出现了几次(作为连续子串)?

你必须在线支持这些操作。

Solution

此处写一种非常规做法,复杂度比后缀平衡树稍劣,但是比后缀平衡树好写很多。洛谷上的模板题卡不过去,最大用时会跑到 \(2.6\) 秒左右。

考虑一些暴力,最简单的暴力显然是使用 KMP 或者 Hash 来判定当前字符串出现的次数。复杂度显然是 \(\mathcal O(qn)\) 的。

如果没有插入和删除操作,那么查询一个模式串在文本串中出现的次数显然可以建后缀机来计算,复杂度 \(\mathcal O(\sum L)\)。

考虑将这两种暴力揉到一起。对于一次询问,我们可以将答案看成两部分的贡献组成:一部分是原串,一部分是新加入的串。对于原串建立后缀机来查询,新串跑暴力即可。但是有可能新串很长,原串很短,这样复杂度就退化了,所以使用根号重构的技巧,当新串的长度超过了一个阈值 \(S\) 就将整个后缀机重构。重构次数显然是 \(\mathcal O(\dfrac{\sum L}{S})\) 的,每次暴力的复杂度是 \(\mathcal O(S)\) 的,因此复杂度为 \(\mathcal O(\dfrac{\sum L}{S}+qS)\),\(\sum L\) 和 \(q\) 同阶时 \(S\) 取 \(\sqrt q\) 即可。

代码实现的时候会发现,当进行删除操作的时候,可能会使得后缀机上的一些节点不能再对答案产生贡献。对于这种情况,容易发现被删除的长度不会超过 \(\mathcal O(S)\),因此在后缀机上跑出答案后用暴力计算出被删除的部分的贡献然后减去即可。

代码不算很难写。

Code
// Cirno is not baka!
#include <bits/stdc++.h>
using namespace std;
#ifdef CIRNO
#include <debug.hpp>
#else
#define Debug(...)
#define Debug_r(...)
#endif
#define For(i, a, b) for (int i = (a); i <= (int)(b); ++i)
#define Rof(i, a, b) for (int i = (a); i >= (int)(b); --i)
#define All(x) x.begin(), x.end()
#define pii pair<int, int>
#define fi first
#define se second
#define i64 long long
#define u64 unsigned long long
#define mkp make_pair
#define epb emplace_back

const int _N = 1.6e6 + 5, mod = 1e9 + 7, inf = 1e9, P = 13331;
template<typename T> void Max(T &x, T y) {x = max(x, y);}
template<typename T> void Min(T &x, T y) {x = min(x, y);}

namespace BakaCirno {
    struct State {
        int ch[26], len, link;
    } s[_N];
    int tot, lst, f[_N];
    vector<int> e[_N];
    void init() {
        For(i, 1, tot) {
            memset(s[i].ch, 0, sizeof s[i].ch);
            s[i].len = s[i].link = f[i] = 0;
            e[i].clear();
        }
        tot = lst = 1;
    }
    void insert(int c) {
        int cur = ++tot; f[cur] = 1;
        s[cur].len = s[lst].len + 1;
        int p = lst;
        while (p && !s[p].ch[c])
            s[p].ch[c] = cur, p = s[p].link;
        if (!p) s[cur].link = 1;
        else {
            int q = s[p].ch[c];
            if (s[q].len == s[p].len + 1) s[cur].link = q;
            else {
                int clone = ++tot;
                s[clone] = s[q];
                s[clone].len = s[p].len + 1;
                s[q].link = s[cur].link = clone;
                while (p && s[p].ch[c] == q)
                    s[p].ch[c] = clone, p = s[p].link;
            }
        }
        lst = cur;
    }
    void build(char *str, int N) {
        init();
        For(i, 0, N - 1) insert(str[i] - 'A');
        For(i, 2, tot) e[s[i].link].epb(i);
        auto dfs = [&](const auto &dfs, int x)->void {
            for (int v: e[x]) dfs(dfs, v), f[x] += f[v];
        };
        dfs(dfs, 1);
    }
    int Q, lstPos, ptr, Lim, mask, delLen, N;
    char base[_N], opt[10], tmpStr[_N], samStr[_N];
    u64 pw[_N];
    void initHash(int n) {
        pw[0] = 1;
        For(i, 1, n) pw[i] = pw[i-1] * P;
    }
    void decode(char *str, int N, int mask) {
        For(i, 0, N - 1) {
            mask = (mask * 131 + i) % N;
            swap(str[i], str[mask]);
        }
    }
    u64 getHash(char *str, int N) {
        u64 res = 0;
        For(i, 0, N - 1) res = res * P + str[i] - 'A' + 1;
        return res;
    }
    int getMatch(char *str, int N, u64 hsh, int M) {
        u64 res = 0;
        int ans = 0;
        For(i, 0, N - 1) {
            res = res * P + str[i] - 'A' + 1;
            if (i - M >= 0) res -= (str[i-M] - 'A' + 1) * pw[M];
            ans += hsh == res;
        }
        return ans;
    }
    void _() {
        cin >> Q >> base;
        initHash(2e6);
        ptr = lstPos = strlen(base) - 1;
        build(base, N = ptr + 1);
        memcpy(samStr, base, sizeof(char) * N);
        Lim = 2e4;
        while (Q--) {
            cin >> opt;
            if (opt[0] == 'A') {
                cin >> tmpStr;
                int len = strlen(tmpStr);
                decode(tmpStr, len, mask);
                memcpy(base + ptr + 1, tmpStr, sizeof(char) * len);
                ptr += len;
            }
            if (opt[0] == 'D') {
                cin >> delLen;
                ptr -= delLen;
                Min(lstPos, ptr);
            }
            if (opt[0] == 'Q') {
                cin >> tmpStr;
                int len = strlen(tmpStr);
                decode(tmpStr, len, mask);
                int cur = 1;
                For(i, 0, len - 1) cur = s[cur].ch[tmpStr[i]-'A'];
                int ans = 0;
                if (cur) ans += f[cur];
                int pl = max(0, lstPos - len + 2);
                u64 hsh = getHash(tmpStr, len);
                ans -= getMatch(samStr + pl, N - pl, hsh, len);
                ans += getMatch(base + pl, ptr - pl + 1, hsh, len);
                cout << ans << '\n';
                mask ^= ans;
            }
            if (ptr - lstPos > Lim) {
                build(base, N = ptr + 1);
                memcpy(samStr, base, sizeof(char) * N);
                lstPos = ptr;
            }
        }
    }
}

void File(const string file) {
    freopen((file + ".in").c_str(), "r", stdin);
    freopen((file + ".out").c_str(), "w", stdout);
}
signed main() {
    double ST = clock();
    // File("P6164_1");
    cin.tie(0)->sync_with_stdio(0);
    int T = 1;
    // cin >> T;
    while (T--) BakaCirno::_();
    Debug((clock() - ST) / CLOCKS_PER_SEC, "s\n");
}

这份代码可以通过 P5212(没有删除操作)。

Bonus

询问变为询问当前串的 \([L,R]\) 区间内有多少模式串,同样强制在线。

会发现暴力的部分仍然不会发生改变,但是后缀机上的部分就不是很好计算贡献了,因为需要统计一个区间内的点数。

会发现这个问题变成了二维数点,即在线询问后缀 link 树上某个节点的子树内有多少满足 \([L,R]\) 限制的点。这可以使用线段树合并提前预处理出每个节点的点集,然后在线段树上直接求区间和即可。复杂度多个 \(\log\),所以需要调一下 \(S\) 的大小。

Code
// Cirno is not baka!
#include <bits/stdc++.h>
using namespace std;
#ifdef CIRNO
#include <debug.hpp>
#else
#define Debug(...)
#define Debug_r(...)
#endif
#define For(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define Rof(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define All(x) x.begin(), x.end()
#define pii pair<int, int>
#define fi first
#define se second
#define i64 long long
#define u64 unsigned long long
#define mkp make_pair
// #define int long long
#define epb emplace_back

const int _N = 4e5 + 5, mod = 1e9 + 7, inf = 1e9;
template<typename T> void Max(T &x, T y) {x = max(x, y);}
template<typename T> void Min(T &x, T y) {x = min(x, y);}

namespace BakaCirno {
    namespace Map {
        char id[127];
        int di[127];
        void init() {
            For(i, 0, 25) id[i] = 'a' + i;
            For(i, 0, 25) id[i + 26] = 'A' + i;
            For(i, 0, 51) di[(int)id[i]] = i;
        }
        inline char gChar(int x) {return id[x];}
        inline int gInt(char x) {return di[(int)x];}
    }
    using Map::gChar, Map::gInt;
    struct State {
        int ch[52], len, link;
    } s[_N];
    int lst, tot, pos[_N];
    vector<int> e[_N];
    int rt[_N], lc[_N*30], rc[_N*30], val[_N*30], nt;
    void pushup(int k) {val[k] = val[lc[k]] + val[rc[k]];}
    #define mid ((l + r) >> 1)
    void update(int &k, int l, int r, int p, int v) {
        k = ++nt;
        if (l == r) return val[k] = v, void();
        if (p <= mid) update(lc[k], l, mid, p, v);
        else update(rc[k], mid + 1, r, p, v);
        pushup(k);
    }
    int merge(int x, int y, int l, int r) {
        if (!x || !y) return x + y;
        int cur = ++nt;
        val[cur] = val[x], lc[cur] = lc[x], rc[cur] = rc[x];
        if (l == r) return val[cur] += val[y], cur;
        lc[cur] = merge(lc[x], lc[y], l, mid);
        rc[cur] = merge(rc[x], rc[y], mid + 1, r);
        return pushup(cur), cur;
    }
    int query(int k, int l, int r, int a, int b) {
        if (a > b || !k) return 0;
        if (l >= a && r <= b) return val[k];
        int res = 0;
        if (a <= mid) res += query(lc[k], l, mid, a, b);
        if (b > mid) res += query(rc[k], mid + 1, r, a, b);
        return res;
    }
    void init() {
        For(i, 1, tot) {
            memset(s[i].ch, 0, sizeof s[i].ch);
            s[i].len = s[i].link = rt[i] = 0;
            pos[i] = -1;
            e[i].clear();
        }
        lst = 1, tot = 1, nt = 0;
    }
    void insert(int c) {
        int cur = ++tot;
        s[cur].len = s[lst].len + 1;
        int p = lst;
        while (p && !s[p].ch[c])
            s[p].ch[c] = cur, p = s[p].link;
        if (!p) s[cur].link = 1;
        else {
            int q = s[p].ch[c];
            if (s[q].len == s[p].len + 1) s[cur].link = q;
            else {
                int clone = ++tot;
                s[clone] = s[q];
                s[clone].len = s[p].len + 1;
                s[q].link = s[cur].link = clone;
                while (p && s[p].ch[c] == q)
                    s[p].ch[c] = clone, p = s[p].link;
            }
        }
        lst = cur;
    }
    void build(const char *str, int n) {
        init();
        For(i, 0, n - 1) {
            insert(gInt(str[i]));
            pos[lst] = i;
        }
        For(i, 2, tot) e[s[i].link].epb(i);
        auto dfs = [&](const auto &dfs, int x)->void {
            // if (~pos[x]) Debug("(", x, pos[x], ' ');
            if (~pos[x]) update(rt[x], 0, n - 1, pos[x], 1);
            for (int v: e[x]) {
                dfs(dfs, v);
                rt[x] = merge(rt[x], rt[v], 0, n - 1);
            }
            // if (~pos[x]) Debug(")", ' ');
        };
        dfs(dfs, 1);
    }
    int Q, N, lstAns, ptr, lstPos, Lim;
    char base[_N];
    inline char gReal(char c) {return gChar((gInt(c) + lstAns) % 52);}
    void gReal(string &str) {for (char &c: str) c = gReal(c);}
    const int P = 13331;
    u64 pw[_N];
    void initHash(int n) {
        memset(pos, -1, sizeof pos);
        pw[0] = 1;
        For(i, 1, n) pw[i] = pw[i - 1] * P;
    }
    u64 getHash(const string &str) {
        u64 res = 0;
        for (char c: str) res = res * P + c;
        return res;
    }
    void _() {
        Map::init();
        initHash(2e5);
        string tmp; cin >> tmp;
        N = tmp.length();
        Debug(N, '\n');
        ptr = lstPos = N - 1;
        For(i, 0, tmp.length() - 1) base[i] = tmp[i];
        build(base, N);
        Lim = N + Q;
        cin >> Q;
        int cnt = 0;
        while (Q--) {
            int opt, L, R; char c;
            cin >> opt;
            if (opt == 1) {
                cin >> c; c = gReal(c);
                base[++ptr] = c;
            }
            if (opt == 2) {
                --ptr;
                Min(lstPos, ptr);
            }
            if (opt == 3) {
                ++cnt;
                string mod;
                cin >> L >> R >> mod;
                L ^= lstAns, R ^= lstAns, gReal(mod);
                // Debug(L, R, mod, '\n');
                if ((int)mod.length() > R - L + 1) {
                    cout << (lstAns = 0) << '\n';
                    continue;
                }
                --L, --R;
                int cur = 1;
                for (char c: mod) cur = s[cur].ch[gInt(c)];
                int ans = 0;
                if (cur)
                    ans += query(rt[cur], 0, N - 1, L + mod.length() - 1, min(R, lstPos));
                u64 modHash = getHash(mod);
                int pl = max(L, lstPos - (int)mod.length() + 2);
                u64 res = 0;
                For(i, pl, min(ptr, R)) {
                    res = res * P + base[i];
                    if (i - (int)mod.length() >= pl)
                        res -= base[i - mod.length()] * pw[mod.length()];
                    if (res == modHash) ++ans;
                }
                cout << (lstAns = ans) << '\n';
            }
            if (ptr - lstPos > Lim) {
                build(base, N = ptr + 1);
                lstPos = ptr;
                Debug("Rebuild!\n");
                // assert(0);
            }
        }
    }
}

void File(const string file) {
    freopen((file + ".in").c_str(), "r", stdin);
    freopen((file + ".out").c_str(), "w", stdout);
}
signed main() {
    File("fantasy");
    cin.tie(0)->sync_with_stdio(0); int T = 1;
    // cin >> T;
    while (T--) BakaCirno::_();
}

标签:非常规,cur,后缀,void,P6164,len,int,str,define
From: https://www.cnblogs.com/hanx16msgr/p/17923531.html

相关文章

  • 使用redbaron删除删除一个.py文件的所有注释,输出到一个新.py文件,文件名加上_nocmts后
    BUG:目前输出的文件中可能会出现缩进错误,待修改。fromredbaronimportRedBarondefremove_comments_with_redbaron(source_py_file,keep_blank_lines):withopen(source_py_file,'r',encoding='utf-8')asfile:red=RedBaron(file.read())co......
  • 写一个MATLAB脚本删除一个.m文件的所有注释和空行,输出到一个新.m文件,文件名加上_modif
    请注意,这个脚本仅处理了最简单的情况,真正的Matlab代码可能包含更复杂的结构,如多行字符串、嵌套的字符串、转义字符等,处理这些情况可能需要更复杂的逻辑。%Specifytheinput.mfilenameinputFileName='originalScript.m';outputFileName=[inputFileName(1:end-2)'_modi......
  • 计算机服务器中了360后缀勒索病毒怎么处理,勒索病毒解密数据恢复
    网络技术的不断发展与应用,越来越多的企业开始走向数字化办公模式,极大地方便了企业的生产运营。但随之而来的网络安全威胁也在不断增加,在本月,云天数据恢复中心陆续接到很多企业的求助,企业的计算机服务器遭到了360后缀勒索病毒攻击,导致企业计算机系统瘫痪,无法正常工作。360后缀勒索病......
  • 后缀排序
    先挂个代码和博客吧blog#include<bits/stdc++.h>usingnamespacestd;#defineriregisterint#definegcgetchartemplate<classT>voidin(T&x){x=0;boolf=0;charc=gc();while(c<'0'||c>'9'){if(c=......
  • SpringBoot学习系列-YAML(后缀为.yml)配置文件使用
    学习使用: YAML 是一种可读性高,以数据为中心的数据序列化格式。什么是序列化?序列化指的是将自定义的对象或者其他数据进行持久化,从而方便进行传输和存储。一般情况下,能够序列化的数据一定能够通过反序列化恢复。注:序列化的目的之一是方便持久化数据,定义本身和持久化应该没啥......
  • 基础后缀数据结构简记
    \[\newcommand{\lcp}{\operatorname{lcp}}\newcommand{\endpos}{\operatorname{endpos}}\newcommand{\link}{\operatorname{link}}\newcommand{\maxl}{\operatorname{maxl}}\newcommand{\minl}{\operatorname{minl}}\]约定\(n\)是字符串长度.\(\lcp(s,t)\......
  • java后缀名file
    Java后缀名文件Java是一种高级编程语言,经常用于开发各种应用程序。在Java编程中,我们经常会遇到以.java为后缀名的文件。这篇文章将为您介绍Java后缀名文件的相关知识,并提供代码示例来帮助您更好地理解。Java后缀名文件的含义在Java中,后缀名为.java的文件是Java源代码文件的标识......
  • 刷题 后缀和 贪心
    2023.12.2cf1903C 解题思路(听说是个常见的形式)  a:第一个部分b:第二个部分c:第三个部分 本题1*a+2*b+3*c......这种形式可以拆成 a+b+c+...... 加上 b+c+...... 加上 c+...... 由以上看出可以构造后缀和 再证明贪心: 当a*n+b*(n+1)>(a+b)*n......
  • 2023-11-23-idea技巧-自定义后缀补全
    Idea技巧-PostfixCompletion在idea中可以使用.xxx进行后缀补全比如.sout如何自定义后缀补全?比如.log在idea中打开设置File|Settings|Editor|General|PostfixCompletion这里定义了上面提到的sout同理点击上图+号新建一个模板,并参考sout写入对应的规则效果......
  • URL绕过-后缀路径模式匹配设置错误绕过过滤器
    @ConfigurationpublicclassUrlMatchConfigextendsWebMvcConfigurationSupport{@OverridepublicvoidconfigurePathMatch(PathMatchConfigurerconfigurer){//setUseSuffixPatternMatch后缀模式匹配,如果设置为true,路径后面不管多少个//都能匹配......