P4248 [AHOI2013]差異 (後綴數組+單調棧)
題目鏈接:傳送門
正文:
首先對於這個公式的前兩項我們可以快速求出,爲。所以我們只需考慮最後一項的和,又因爲每個對應排序後的後綴的位置,所以這步可以轉化爲在排序後的後綴數組上統計答案。
首先最後一項 的值,設,不訪假設,那麼等於,即區間的數組中的最小的值 的值是貢獻。如果最小高度有多個,那麼對規定最左邊的最小高度起貢獻。
因爲最後一項的是所有元組,所以我們可以用單調棧維護每個貢獻的區間即可,
代碼:
#include<bits/stdc++.h>
#define mset(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int N=5e5+10;//下標從1開始
int rak[N],sa[N],tp[N],c[N],height[N];
//最後的rak[i]=p 與sa[p]=i ,11對應
void SA(char *s,int n)//這個基數排序的版本中桶的大小爲第一關鍵詞rak的最大值
{
int m=128;//桶的大小,會慢慢變大,最大爲n,但初始時是字符的最大值
//首先基數排序初始化rak數組和sa數組
//第一輪基數排序,如果s的最大值很大,可改用快速排序
for(int i=0; i<=m; ++i) c[i]=0;
for(int i=1; i<=n; ++i) c[rak[i]=s[i]]++;
for(int i=1; i<=m; ++i) c[i]+=c[i-1];
for(int i=n; i>=1; --i) sa[c[s[i]]--]=i; //所有後綴長度爲1的字符串已求出
for(int k=1,p ; k < n; k<<=1 ) //p是一個計數器,現在還沒用。
{
/*tp[i]:第二關鍵詞排名爲i的位置爲tp[i]*/
p=0;
for(int i=n-k+1; i<=n; ++i) tp[++p]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k) tp[++p]=sa[i]-k;
/*基數排序求出2k長度的sa數組*/
for(int i=0; i<=m; ++i) c[i]=0;
for(int i=1; i<=n; ++i) c[rak[i]]++;
for(int i=1; i<=m; ++i) c[i]+=c[i-1];
for(int i=n; i>=1; --i) sa[ c[rak[tp[i]]]-- ]=tp[i];
/*求該輪的rank數組*/
//tp纔是上一輪的rak,現在要利用上輪的rak和這輪的sa求這輪的rank
std::swap(rak,tp);
rak[sa[1]]=p=1;
for(int i=2,a,b; i<=n; ++i)
{
a=sa[i],b=sa[i-1];
if(tp[a]==tp[b]&&((a+k<=n&& b+k<=n&&tp[a+k]==tp[b+k])||(a+k > n&& b+k > n))) rak[a]=p;
else rak[a]=++p;
}
if(p>=n) break;//可以提前退出
m=p;
}//計算高度
int k=0;
for(int i=1; i<=n; ++i)
{
if(k) k--;//i ,
int j=sa[rak[i]-1];
if(j==0) continue;
while(s[j+k]==s[i+k]) ++k;
height[rak[i]]=k;
}
}
char s[N];
int S[N],top;
int left[N],right[N];
int main()
{
scanf("%s",s+1);
int ls=strlen(s+1);
SA(s,ls);
top=0;
for(int i=2;i<=ls;++i)
{
while(top > 0&& height[S[top]]> height[i]) top--;
S[++top]=i;
if(top==1) left[i]=2;
else left[i]=S[top-1]+1;
}
top=0;
for(int i=ls;i>=2;--i)
{
while(top>0 && height[S[top]] >=height[i]) top--;
S[++top]=i;
if(top==1) right[i]=ls;
else right[i]=S[top-1]-1;
}
long long ans=(1ll+ls)*(ls-1)*(ls)/2ll;
for(int i=2;i<=ls;++i)
{
ans-=2ll*height[i]*(right[i]-i+1)*(i-left[i]+1);
}
printf("%lld\n",ans);
}