题意:
给定字符串s,
对于s中每一种本质不同的串,设出现次数为cnt,那么对答案的贡献为cnt2
要求计算所有本质不同的串的贡献和
数据范围:|s|<=1e5
解法:
看了别人的题解,是将贡献变为:
每个子串和之前出现的相同子串匹配(不包括自己),
设匹配次数为cnt,则贡献为2*cnt+1
为什么可以这样呢?
1.先验证一下是否正确:
假设串为aaa,现在要计算子串a的贡献
第一个a的贡献为2*0+1=1,
第二个a的贡献为2*1+1=3,
第三个a的贡献为2*2+1=5,
总贡献为1+3+5=9,而按平方算贡献的话,贡献为3^2=9,是一样的.
2.原理:
将n^2拆分成了1+3+5+7+9...这个等差数列,
这个等差数列的前n项和恰好是n^2
而第i项的通项公式为(i-1)*2+1,(也可以是2*i-1,做法应该差不多)
(学到了新操作)
把贡献中的+1提出来,每一步的贡献2*cnt+1就变为2*cnt,
设总出现次数为tot,那么提出来的+1部分的总贡献为tot
所有本质不同子串的tot部分总和为n*(n+1)/2,可以直接算
(因为出现tot次,贡献为tot,即每个子串的贡献为1,子串总数就是贡献数)
2*cnt部分的总贡献,用单调递增栈计算就行了,
做法是后缀数组+单调栈的套路了(height[]+单调栈).
这里就不细说了.
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=1e5+5;
struct SA{
static const int N=2e6+5;
char s[N];
int sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[N];
int ht[N];
int n;//字符串长度
int m=300;//初始字符集大小
bool cmp(int x,int y,int w){
return oldrk[x]==oldrk[y]&&oldrk[x+w]==oldrk[y+w];
}
void init(){//init
memset(cnt,0,sizeof cnt);
m=300;
}
void getSA(){
int i,p,w;
for(i=1;i<=n;i++)cnt[rk[i]=s[i]]++;
for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
for(i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;
//
for(w=1;w<n;w<<=1,m=p){
for(p=0,i=n;i>n-w;i--)id[++p]=i;
for(i=1;i<=n;i++)if(sa[i]>w)id[++p]=sa[i]-w;
memset(cnt,0,sizeof cnt);
for(i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++;
for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
for(i=n;i>=1;i--)sa[cnt[px[i]]--]=id[i];
memcpy(oldrk,rk,sizeof rk);
for(p=0,i=1;i<=n;i++){
rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;
}
}
}
void getHT(){
for(int i=1,k=0;i<=n;i++){
if(k)k--;
while(s[i+k]==s[sa[rk[i]-1]+k])k++;
ht[rk[i]]=k;
}
}
}sa;
struct Node{
int h,len;
}stk[maxm];
signed main(){
//input
int T;scanf("%d",&T);
while(T--){
sa.init();
//
char *s=sa.s;
int &n=sa.n;
scanf("%s",s+1);
n=strlen(s+1);
//
sa.getSA();
sa.getHT();
//
int *ht=sa.ht;
ll ans=0;
ans+=1ll*n*(n+1)/2;
ll cnt=0;
int head=0;
for(int i=2;i<=n;i++){
Node temp={ht[i],1};
while(head&&stk[head].h>temp.h){
temp.len+=stk[head].len;
cnt-=1ll*stk[head].h*stk[head].len;
head--;
}
stk[++head]=temp;
cnt+=1ll*temp.h*temp.len;
ans+=cnt*2;
}
cout<<ans<<endl;
}
return 0;
}