BZOJ 4943: [Noi2017]蚯蚓 字符串哈希

title

BZOJ 4943

LUOGU 3823

简化题意:

给出 \(n\) 个字符,初始每个字符单独成字符串。支持 \(m\) 次操作,每次为一下三种之一:

  • 1 i j:将以 \(i\) 结尾的串和以 \(j\) 开头的串连到一起。

  • 2 i :将 \(i\) 所在串从 \(i\) 位置和 \(i\) 下一个位置之间断开。

  • 3 S k :对于字符串 \(S\) 每个长度为 \(k\) 的子串,统计它在这 \(n\) 个字符组成所有字符串中出现的次数,求所有统计结果的乘积模 \(998244353\) 的结果。

\(n\leqslant 2×10^5\)\(m\leqslant 5×10^5\)\(\sum|S|\leqslant 10^7\)\(k\leqslant 50\)2 i 操作次数 \(c\leqslant 1000\)

analysis

读过题目,大佬直觉要用链表模拟蚯蚓队列,蒟蒻看过题解方可知道(逃。

解释一下三个操作的具体做法:

  • 对于每一个1号操作,计算所有新增的,长度在50以内的子串的哈希值,并加入哈希表中。

  • 对于每一个2号操作,计算所有被切断的,长度在50以内的子串的哈希值,并在哈希表中除去。

  • 对于每一个3号操作,计算每个子串的哈希值,并在哈希表中找到它们的出现次数,并相乘得到答案。

哈希表就默认都会了,那么就来分析一下复杂度。

显然,2号操作的总复杂度为 \(O(CK^2)\),3号操作的总复杂度为 \(O(\sum|S|)\)

对于1号操作,若不存在2号操作,那么最终可能形成的长度在 \(K\) 以内的子串是有限的,为 \(O(NK)\) 个。
而2号操作至多截断了 \(O(CK^2)\) 个子串,也就是说,1号操作的总复杂度应为 \(O(NK+CK^2)\)

时间复杂度 \(O(NK+CK^2+\sum|S|)\)

一个不太好的消息是,我这个代码会因为第 \(96\) 行的 \(scanf\) 而在 \(BZOJ\) 被判 \(CE\)\(BZOJ\) 说是什么 \(scanf\) 返回值忽略了,反正我是不会搞了(雾。

code

#include<bits/stdc++.h>//吸氧时间少一半
using namespace std;
typedef unsigned long long ull;
const int maxn=2e5+10,maxm=1e7+1,maxh=2e7+1,p=10233333,mod=998244353;

char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1, ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x)
{
    if (!x) *fe++=48;
    if (x<0) *fe++='-', x=-x;
    T num=0, ch[20];
    while (x) ch[++num]=x%10+48, x/=10;
    while (num) *fe++=ch[num--];
    *fe++='\n';
}

struct Hash_Table
{
    int head[p],len[maxh],cnt[maxh],Next[maxh],tot;
    ull val[maxh];
    inline void add(int l,ull v)
    {
        int x=v%p, i, last=0;
        for (i=head[x]; i; last=i, i=Next[i])
            if (len[i]==l && val[i]==v) break;//找到这个字符串,停下
        if (i) ++cnt[i];//已经出现过,记录出现次数
        else//否则,新开一个节点,记录这个字符串
        {
            if (!last) head[x]=++tot;
            else Next[last]=++tot;
            len[tot]=l, val[tot]=v, ++cnt[tot];
        }
    }

    inline void erase(int l,ull v)
    {
        int x=v%p;
        for (int i=head[x]; i; i=Next[i])
            if (len[i]==l && val[i]==v) --cnt[i];//删掉这个字符串
    }

    inline int find(int l,ull v)
    {
        int x=v%p;
        for (int i=head[x]; i; i=Next[i])
            if (len[i]==l && val[i]==v) return cnt[i];//查询这个字符串所出现的次数
        return 0;
    }
}M;

int a[maxn],head[maxn],Next[maxn];
ull base[55],vx,vy,ans;
char ch[maxm];
int main()
{
    int n,m,opt,x,y;
    read(n); read(m); base[0]=1;
    for (int i=1; i<50; ++i) base[i]=base[i-1]*233;//Hash数组
    for (int i=1; i<=n; ++i) read(a[i]), M.add(1,a[i]^'0');
    while (m--)
    {
        read(opt);
        if (opt==1)
        {
            read(x),read(y); Next[x]=y, head[y]=x; vx=0;//链表模拟
            for (int i=1, u=x; i<50 && u; ++i, u=head[u])
            {
                vx+=(a[u]^'0')*base[i-1], vy=0;
                for (int j=1, v=y; i+j<=50 && v; ++j, v=Next[v]) vy=vy*233+(a[v]^'0'), M.add(i+j,vx*base[j]+vy);
            }
        }
        else if (opt==2)
        {
            read(x); y=Next[x], Next[x]=head[y]=0, vx=0;
            for (int i=1, u=x; i<50 && u; ++i, u=head[u])
            {
                vx+=(a[u]^'0')*base[i-1], vy=0;
                for (int j=1, v=y; i+j<=50 && v; ++j, v=Next[v]) vy=vy*233+(a[v]^'0'), M.erase(i+j,vx*base[j]+vy);
            }
        }
        else
        {
            scanf("%s",ch+1); read(x); vx=0, ans=1;
            for (int i=1; i<x; ++i) vx=vx*233+ch[i];
            for (int i=x; ch[i]; ++i) vx=vx*233+ch[i], ans=ans*M.find(x,vx)%mod, vx-=ch[i-x+1]*base[x-1];
            write(ans);
        }
    }
    flush();
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11360637.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值