【雅礼集训2017】字符串【后缀自动机】【数据分治】
題意:給定一個字符串SSS和mmm個區間[li,ri][l_i,r_i][li?,ri?],qqq次詢問,每次給定長度為kkk的字符串www和區間[a,b][a,b][a,b],求對于所有i∈[a,b]i\in[a,b]i∈[a,b],www在[li,ri][l_i,r_i][li?,ri?]內的子串在SSS中出現次數之和。
∣S∣,m,∑∣w∣≤105|S|,m,\sum|w|\leq10^5∣S∣,m,∑∣w∣≤105
看上去很不可做,但是有一個很難注意到的特殊性質:所有www串長相等,所以kq≤105kq\leq10^5kq≤105。后面記kq=wkq=wkq=w
所以k,qk,qk,q中的較小值是根號級別的,考慮數據分治
首先肯定要先建出SSS的后綴自動機
當k<qk<qk<q時,字符串很短,直接開k2k^2k2個vector記錄所有區間出現的位置,然后暴力枚舉www的子串,在對應的vector用aaa和bbb二分一下算出有多少個區間,乘上在后綴自動機上的size。復雜度O(qk2log?n)=O(wwlog?n)O(qk^2\log n)=O(w\sqrt w\log n)O(qk2logn)=O(ww?logn)
當k>qk>qk>q時,詢問很少,可以每次單獨處理。每次讀入www后先預處理出www的每個前綴iii 最長的 是SSS的子串 的 后綴長度LiL_iLi?。
然后暴力把[a,b][a,b][a,b]中的區間掛到rrr上,從左到右掃一遍,設當前處理[l,r][l,r][l,r],如果Lr<r?l+1L_r<r-l+1Lr?<r?l+1,說明這個子串沒有出現過,直接跳過;否則在fail樹上倍增找到最靠上的滿足lenp≥r?l+1len_p\geq r-l+1lenp?≥r?l+1的結點ppp,這個子串就出現了sizpsiz_psizp?次。復雜度O(qmlog?n)=O(mwlog?n)O(qm\log n)=O(m\sqrt w\log n)O(qmlogn)=O(mw?logn)
某個k=qk=qk=q的點用SOLVE2會卡常,所以特判成了SOLVE1
#include <iostream> #include <cstdio> #include <cstring> #include <cctype> #include <vector> #include <algorithm> #define MAXN 200005 using namespace std; int ch[MAXN][26],fa[MAXN],tot=1,las=1; int len[MAXN],siz[MAXN]; void insert(int c) {int p=las,cur=++tot;len[cur]=len[las]+1,las=cur;for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;if (!p) fa[cur]=1;else{int q=ch[p][c];if (len[q]==len[p]+1) fa[cur]=q;else{int _q=++tot;len[_q]=len[p]+1;fa[_q]=fa[q],fa[q]=fa[cur]=_q;memcpy(ch[_q],ch[q],sizeof(ch[q]));for (;ch[p][c]==q;p=fa[p]) ch[p][c]=_q;}}siz[cur]=1; } int a[MAXN],c[MAXN],up[MAXN][20]; inline void build(int n) {for (int i=1;i<=tot;i++) ++c[len[i]];for (int i=1;i<=n;i++) c[i]+=c[i-1];for (int i=tot;i;i--) a[c[len[i]]--]=i;for (int i=1;i<=tot;i++){up[a[i]][0]=fa[a[i]];for (int j=1;j<20;j++) up[a[i]][j]=up[up[a[i]][j-1]][j-1];}for (int i=tot;i;i--) if (fa[a[i]]) siz[fa[a[i]]]+=siz[a[i]]; } int n,m,k,q,l[MAXN],r[MAXN]; char s[MAXN],w[MAXN]; typedef long long ll; namespace SOLVE1 {vector<int> lis[405][405];int pos[MAXN];void main(){for (int i=1;i<=m;i++) lis[l[i]][r[i]].push_back(i);while (q--){int a,b;scanf("%s%d%d",w+1,&a,&b);++a,++b;ll ans=0;for (int i=1;i<=k;i++){int now=1;for (int j=i;j<=k;j++){now=ch[now][w[j]-'a'];if (!now) break;ans+=(ll)siz[now]*(upper_bound(lis[i][j].begin(),lis[i][j].end(),b)-upper_bound(lis[i][j].begin(),lis[i][j].end(),a-1));}}printf("%lld\n",ans);}} } namespace SOLVE2 {vector<int> lis[MAXN];int pos[MAXN],maxl[MAXN];void main(){while (q--){int a,b;scanf("%s%d%d",w+1,&a,&b);++a,++b;ll ans=0;int now=1,curl=0;for (int i=a;i<=b;i++) lis[r[i]].push_back(l[i]);for (int i=1;i<=k;i++) {while (now&&!ch[now][w[i]-'a']) now=fa[now],curl=len[now];now=ch[now][w[i]-'a'],++curl;if (!now) now=1,curl=0;pos[i]=now,maxl[i]=curl;}for (int p=1;p<=k;p++)for (int j=0;j<(int)lis[p].size();j++){int u=pos[p],lim=p-lis[p][j]+1;if (maxl[p]<lim) continue;for (int i=19;i>=0;i--)if (len[up[u][i]]>=lim)u=up[u][i];ans+=siz[u];}printf("%lld\n",ans);for (int i=a;i<=b;i++) lis[r[i]].clear();}} } int main() {scanf("%d%d%d%d",&n,&m,&q,&k);scanf("%s",s+1);for (int i=1;i<=n;i++) insert(s[i]-'a');build(n);for (int i=1;i<=m;i++) scanf("%d%d",&l[i],&r[i]),++l[i],++r[i];if (k<=q) SOLVE1::main();elseSOLVE2::main();return 0; } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的【雅礼集训2017】字符串【后缀自动机】【数据分治】的全部內容,希望文章能夠幫你解決所遇到的問題。