UOJ#219/BZOJ4650 [NOI2016]优秀的拆分 字符串 SA ST表
原文链接http://www.cnblogs.com/zhouzhendong/p/9025092.html
题目传送门 - UOJ#219 (推荐,题面清晰)
题目传送门 - BZOJ4650
题意
如果一个字符串可以被拆分为AABB的形式,其中AA和BB是任意非空字符串,则我们称该字符串的这种拆分是优秀的。
现在给出一个长度为n的字符串S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。
多组数据,数据组数≤10,n≤30000
题解
考虑先处理AA类型的字符串,对于每一个位置i,分别处理以i位置开头或结尾的AA类型字符串个数。
如果完成了这个处理,那么最后的统计便毫不费力。(注意开longlong)
考虑如何处理。
首先,枚举A串的长度,记为L。我们设置一些关键点,依次为1L,2L,3L,⋯,iL,(i+1)L,⋯。
考虑处理相邻两个关键点对答案的贡献。对于第i对相邻的关键点,我们需要得到一个特殊的最长公共子串。
这个最长公共子串由结尾为iL,(i+1)L的长度不大于L的最长公共后缀(LCS) 和 开头为iL,(i+1)L的长度不大于L的最长公共前缀(LCP) 拼接而成。
于是你可以通过删除一些这个最长公共子串的后缀和前缀,使得包含两个关键点的相同串首尾相接,来得到长度为2L的AA类型字符串。
这样的串对于一开始要处理的那个东西有一个区间的贡献。可以用差分来搞定。
至于求LCS和LCP,我们只需要先后缀数组+ST表预处理一下,就可以了。
时间复杂度O(nlogn)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #include <bits/stdc++.h> #define rank r_a_n_k using namespace std; typedef long long LL; const int N=60005; int T,n,m,tmp[N],SA[N],rank[N],tax[N],height[N]; int ST[N][18],A[N],B[N]; char s[N]; void Sort( int n){ for ( int i=0;i<=m;i++) tax[i]=0; for ( int i=1;i<=n;i++) tax[rank[i]]++; for ( int i=1;i<=m;i++) tax[i]+=tax[i-1]; for ( int i=n;i>=1;i--) SA[tax[rank[tmp[i]]]--]=tmp[i]; } bool cmp( int rk[], int x, int y, int w){ return rk[x]==rk[y]&&rk[x+w]==rk[y+w]; } void Suffix_Array( char s[], int n){ memset (SA,0, sizeof SA); memset (tmp,0, sizeof tmp); memset (rank,0, sizeof rank); memset (height,0, sizeof height); for ( int i=1;i<=n;i++) rank[i]=s[i],tmp[i]=i; m=127; Sort(n); for ( int w=1,p=0;p<n;w<<=1,m=p){ p=0; for ( int i=n-w+1;i<=n;i++) tmp[++p]=i; for ( int i=1;i<=n;i++) if (SA[i]>w) tmp[++p]=SA[i]-w; Sort(n); swap(tmp,rank); rank[SA[1]]=p=1; for ( int i=2;i<=n;i++) rank[SA[i]]=cmp(tmp,SA[i],SA[i-1],w)?p:++p; } for ( int i=1,j,k=0;i<=n;height[rank[i++]]=k) for (k=max(k-1,0),j=SA[rank[i]-1];s[i+k]==s[j+k];k++); } void GetST( int n){ memset (ST,0, sizeof ST); for ( int i=1;i<=n;i++){ ST[i][0]=height[i]; for ( int j=1;j<18;j++){ ST[i][j]=ST[i][j-1]; if (i-(1<<(j-1))>0) ST[i][j]=min(ST[i][j],ST[i-(1<<(j-1))][j-1]); } } } int QueryST( int L, int R){ int val= floor ( log (R-L+1)/ log (2)); return min(ST[L+(1<<val)-1][val],ST[R][val]); } int LCP( int i, int j){ i=rank[i],j=rank[j]; return QueryST(min(i,j)+1,max(i,j)); } int LCS( int i, int j){ return LCP(n*2-i+2,n*2-j+2); } int main(){ scanf ( "%d" ,&T); while (T--){ memset (s,0, sizeof s); scanf ( "%s" ,s+1); n= strlen (s+1); s[n+1]= '#' ; for ( int i=n;i>=1;i--) s[n*2-i+2]=s[i]; Suffix_Array(s,n*2+1); GetST(n*2+1); memset (A,0, sizeof A); memset (B,0, sizeof B); for ( int L=1;L<=n;L++) for ( int i=1;i<=n/L-1;i++){ int lcp=min(LCP(L*i,L*(i+1)),L); int lcs=min(LCS(L*i,L*(i+1)),L); if (lcp+lcs<=L) continue ; int l=i*L-lcs+1,r=(i+1)*L+lcp-1; int cov=lcp+lcs-(L+1); B[l]++,B[l+cov+1]--; A[r-cov]++,A[r+1]--; } for ( int i=1;i<=n;i++) A[i]+=A[i-1],B[i]+=B[i-1]; LL ans=0; for ( int i=1;i<=n;i++) ans+=1LL*A[i]*B[i+1]; printf ( "%lld\n" ,ans); } return 0; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现