3238: [Ahoi2013]差异
Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 4128 Solved: 1869
[Submit][Status][Discuss]
Description
Input
一行,一个字符串S
Output
一行,一个整数,表示所求值
Sample Input
cacao
Sample Output
54
HINT
2<=N<=500000,S由小写英文字母组成
Source
sa:
前面的求和一下。后面的考虑从sa上面做。
答案是
∑i∑nj=1i到j的minhei
∑
i
∑
j
=
1
n
i
到
j
的
m
i
n
h
e
i
显然不能n^2做。考虑minhei影响到的区间,我们可以单调栈处理出来,然后就是他的左区间*右区间
#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
const int Manx = 1010000;
int n, m, tot, p, t;
long long Ans;
int h[Manx], l[Manx], r[Manx];
int x[Manx], sa[Manx], wv[Manx], w[Manx], y[Manx], hei[Manx];
char Sr[Manx];
int main()
{
int i, j;
scanf("%s",Sr + 1);
n = strlen(Sr + 1);
m = 255;
for(i = 1; i <= n; ++i) w [x[i] = Sr[i]] ++;
for(i = 2; i <= m; ++i) w[i] += w[i - 1];
for(i = n; i >= 1; --i) sa[w[x[i]] --] = i;
for(j = 1; j <= n; j <<= 1)
{
m = p;
p = 0;
for(i = n; i >= n - j + 1; --i) y[++ p] = i;
for(i = 1; i <= n; ++i)
if(sa[i] > j) y[++ p] = sa[i] - j;
for(i = 1; i <= m; ++i) w[i] = 0;
for(i = 1; i <= n; ++i) ++w[x[i]];
for(i = 2; i <= m; ++i) w[i] += w[i - 1];
for(i = n; i >= 1; --i) sa[w[x[y[i]]]--] = y[i];
swap(x, y);
p = 0;
for(i = 1; i <= n; ++i)
x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + j] == y[sa[i - 1] + j]) ? p : ++p;
if(n == p) break;
}
// for(i = 1; i <= n; ++i) printf("%d ", sa[i]);
// printf("\n");
for(i = 1; i <= n; ++i)
{
tot -= (tot > 0);
j = sa[x[i] - 1];
while(Sr[i + tot] == Sr[j + tot]) tot++;
hei[x[i]] = tot;
}
Ans += (long long)(n + 1) * n / 2 * (n - 1);
hei[0] = -1; hei[n + 1] = -1;
t = 0; h[t] = 0;
for(i = 1; i <= n; ++i)
{
while(hei[h[t]] > hei[i]) t --;
l[i] = h[t] + 1;
h[++t] = i;
}
t = 0; h[t] = n + 1;
for(i = n; i >= 1; --i)
{
while(hei[h[t]] >= hei[i]) t --;
r[i] = h[t] - 1;
h[++t] = i;
}
for(i = 1; i <= n; ++i)
Ans -= (long long)2 * hei[i] * (r[i] - i + 1) * (i - l[i] + 1);
printf("%lld",Ans);
fclose(stdin);fclose(stdout);
}
sam:
前面定值求和即可。考虑后面怎么求。题目让我们求后缀的公共前缀,这玩意是sa求得,sam干不了这事。
我们考虑把串反转一下,那么久变成了求前缀的最长公共后缀。这个东西就好做了,我们考虑在parent树上跳的过程实际上就是不断的在取后缀的过程,那么对于2个前缀,我们就一直跳,直到跳到他们的lca,这必然是他们的最长公共后缀了。那么问题就变成了对于一个状态,他是多少个前缀的lca。简单dp
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
int n,m;
inline int read()
{
char c;
bool pd=0;
while((c=getchar())>'9'||c<'0')
if(c=='-') pd=1;
int res=c-'0';
while((c=getchar())>='0'&&c<='9')
res=(res<<3)+(res<<1)+c-'0';
return pd?-res:res;
}
const int N=1100000;
char sr[N];
int fa[N],go[N][26],val[N],vis[N],last,tot;
inline void extend(int x)
{
int p=last;
int np=last=++tot;
val[np]=val[p]+1;
while(!go[p][x]) go[p][x]=np,p=fa[p];
if(!p) fa[np]=1,++vis[1];
else
{
int q=go[p][x];
if(val[q]==val[p]+1) fa[np]=q,++vis[q];
else
{
int nq=++tot;
fa[nq]=fa[q];
vis[nq]=2;
fa[q]=fa[np]=nq;
val[nq]=val[p]+1;
for(int i=0;i<=25;++i) go[nq][i]=go[q][i];
while(go[p][x]==q) go[p][x]=nq,p=fa[p];
}
}
}
int q[N];
int size[N];
long long ans;
int main()
{
// freopen("3238.in","r",stdin);
// freopen(".out","w",stdout);
scanf("%s",sr+1);
n=strlen(sr+1);
m=n/2;
for(int i=1;i<=m;++i) swap(sr[i],sr[n-i+1]);
last=tot=1;
for(int i=1;i<=n;++i)
{
size[tot+1]=1;
extend(sr[i]-'a');
}
int t=0,w=0;
for(int i=1;i<=tot;++i)
if(!vis[i]) q[++w]=i;
while(t<w)
{
int u=q[++t];
if(!(--vis[fa[u]])) q[++w]=fa[u];
ans-=(long long)size[fa[u]]*size[u]*val[fa[u]];
size[fa[u]]+=size[u];
}
ans<<=1;
ans+=(long long)(n+1)*n/2*(n-1);
cout<<ans;
}