title
简化题意:
给出 \(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; }