【BZOJ4650】优秀的拆分(NOI2016)-后缀数组+RMQ+差分
测试地址:优秀的拆分
做法:本题需要用到后缀数组+RMQ+差分。
容易想到,令分别为以点结尾或开头的形如的字符串数,那么答案就是。那么我们怎么求这两个数组呢?
对于一个长为的字符串,在它之后还有一个的充要条件是:将字符串分成若干长度为的段,那么字符串必定经过或贴住一个分隔两段的点(以下简称为分割点),那么紧接在字符串的一个长度为的子串,也必定经过或贴住一个分割点,并且它和分割点的相对位置和字符串相同,那么两个子串相同就等价为,对相邻的两个分割点,从两个分割点出发往左的一部分相等,往右的一部分也相等,这两个部分合起来长度为。
转化了这个条件之后,我们又能做什么呢?注意到上面的条件仅和相邻的分割点有关,所以我们枚举,再枚举分割点,这样枚举的时间复杂度是的。接下来我们要找到从这两个分割点往左的最长相同部分,注意到这就是两个前缀的最长公共后缀,把字符串倒过来做个后缀数组,再搭配ST表做RMQ可以做到询问。向右的公共部分同理,就是求两个后缀的最长公共前缀。那么只要那个长度为的子串在第一个分割点向左向右划出的这么一个区间内,它的后面一个长为的子串就和它相同。注意我们每次只考虑左端点在第一个分割点之前,到它前面一个分割点为止的子串的贡献。此时为了统计贡献,我们需要一个支持区间加的结构,显然可以差分,最后再做个前缀和就能得到和了。这样我们就完成了此题,时间复杂度为。
(这道题好是好,就是这个数据的区分度太傻逼了,哈希暴力95,我后缀数组写挂都有65……完全没有让人写正解的诱惑力)
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,x[30010],y[30010],s[30010],cnt[30010];
int SA[30010],Rank[30010],height[30010];
int rSA[30010],rrank[30010],rheight[30010];
int len[30010],f[30010][16],rf[30010][16];
ll pre[30010],nxt[30010];
char S[30010],rS[30010];
void init()
{
scanf("%s",S+1);
S[0]='#';
n=strlen(S)-1;
for(int i=1;i<=n;i++)
rS[i]=S[n-i+1];
}
void calc_SA(char *S,int *rank,int *SA)
{
for(int i=1;i<=n;i++)
x[i]=S[i]-'a'+1;
int m=26,p=1;
while(p<n)
{
for(int i=1;i<=n;i++)
{
if (i+p<=n) y[i]=x[i+p];
else y[i]=0;
}
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) cnt[y[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=1;i<=n;i++) s[cnt[y[i]]--]=i;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) cnt[x[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) SA[cnt[x[s[i]]]--]=s[i];
m=0;
for(int i=1;i<=n;i++)
{
if (i==1||x[SA[i]]!=x[SA[i-1]]||y[SA[i]]!=y[SA[i-1]]) m++;
rank[SA[i]]=m;
}
if (m==n) break;
for(int i=1;i<=n;i++) x[i]=rank[i];
p<<=1;
}
}
void calc_height(int *height,int *rank,int *SA,char *S)
{
int last=0;
for(int i=1;i<=n;i++)
{
if (rank[i]==n)
{
height[rank[i]]=last=0;
continue;
}
while(S[i+last]==S[SA[rank[i]+1]+last]) last++;
height[rank[i]]=last;
last=max(last-1,0);
}
}
void pre_rmq()
{
int bit=0;
for(int i=1;i<=n;i++)
{
if ((1<<(bit+1))<i) bit++;
len[i]=bit;
}
for(int i=1;i<=n;i++)
f[i][0]=height[i],rf[i][0]=rheight[i];
for(int i=1;i<=15;i++)
for(int j=1;j<=n-(1<<i)+1;j++)
{
f[j][i]=min(f[j][i-1],f[j+(1<<(i-1))][i-1]);
rf[j][i]=min(rf[j][i-1],rf[j+(1<<(i-1))][i-1]);
}
}
int LCP(int x,int y)
{
if (x<1||x>n||y<1||y>n) return 0;
int a=Rank[x],b=Rank[y],l=min(a,b),r=max(a,b)-1,p=len[r-l+1];
return min(f[l][p],f[r-(1<<p)+1][p]);
}
int LCS(int x,int y)
{
x=n-x+1,y=n-y+1;
if (x<1||x>n||y<1||y>n) return 0;
int a=rrank[x],b=rrank[y],l=min(a,b),r=max(a,b)-1,p=len[r-l+1];
return min(rf[l][p],rf[r-(1<<p)+1][p]);
}
void work()
{
memset(pre,0,sizeof(pre));
memset(nxt,0,sizeof(nxt));
for(int L=1;L<=(n>>1);L++)
for(int i=L+1;i<=n;i+=L)
{
int x=LCP(i,i+L),y=LCS(i-1,i+L-1);
if (x+y>=L)
{
int l=max(i-L,i-y),r=min(i-1,i+x-L);
if (l>r) continue;
nxt[l]++,nxt[r+1]--;
pre[l+(L<<1)-1]++,pre[r+(L<<1)]--;
}
}
for(int i=1;i<=n;i++)
pre[i]+=pre[i-1],nxt[i]+=nxt[i-1];
ll ans=0;
for(int i=1;i<n;i++)
ans+=pre[i]*nxt[i+1];
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&T);
while(T--)
{
init();
calc_SA(S,Rank,SA);
calc_height(height,Rank,SA,S);
calc_SA(rS,rrank,rSA);
calc_height(rheight,rrank,rSA,rS);
pre_rmq();
work();
}
return 0;
}