【AHOI2013】差异(后缀数组+单调栈)

描述见原题。

分析

首先可以分出一堆常数为:(N-1)*(N+1)*N/2,可以自行推。

剩下的就是减去两两后缀之间LCP的2倍了。我们知道对于两个后缀i、j,它们的LCP取的是Height[i+1]~Height[j]之间的最小值,那么问题就是对于所有的区间,取一个最小值。

转化一下问题,转化为求每个Height可以在多少区间里做最小值,直接得解。一个想法是维护两个数组,分别记录Height[i]向左和向右遇到的第一个小于等于它的Height下标。

另一个很巧妙的方法是维护一个单调栈,使得底部的Height总小于等于顶部Height。设Height[p]是第一个小于等于Height[i]的,那么设f(i)=f(p)+(i-p)*Height[i],且ans=\sum_{i=1}^N{f(i)}。由于栈是单调的,对于后进来的Height的位置,之前的Height在这之间也可以成为最小值。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>

using namespace std;

typedef long long LL;

const int MAXN=500005;

char s[MAXN];
int a[MAXN];
int RANK[MAXN];
int cnt[MAXN];
int tmp[MAXN];
int SA[MAXN];
int height[MAXN];
int N,M;
LL f[MAXN];

struct data{int id,val;}stk[MAXN];

void init()
{
	scanf("%s",s);
	N=strlen(s);
	for(int i=0;i<N;i++) a[i+1]=s[i];
}

void Rsort()
{
	for(int i=0;i<=M;i++) cnt[i]=0;
	for(int i=1;i<=N;i++) cnt[RANK[tmp[i]]]++;
	for(int i=1;i<=M;i++) cnt[i]+=cnt[i-1];
	for(int i=N;i>=1;i--) SA[cnt[RANK[tmp[i]]]--]=tmp[i];
}

int cmp(int *f,int x,int y,int w) {return f[x]==f[y]&&f[x+w]==f[y+w];}

void suffix()
{
	for(int i=1;i<=N;i++) RANK[i]=a[i],tmp[i]=i;
	M=200,Rsort();
	
	for(int w=1, p=1, i; p<N; w+=w, M=p)
	{
		for(p=0, i=N-w+1; i<=N; i++) tmp[++p]=i;
		for(i=1; i<=N; i++) if(SA[i]>w) tmp[++p]=SA[i]-w;
		
		Rsort();swap(RANK,tmp);RANK[SA[1]]=p=1;
		
		for(i=2; i<=N; i++) RANK[SA[i]]= cmp(tmp,SA[i],SA[i-1],w)? p: ++p; 
	}
	
	int j,k=0;
	for(int i=1; i<=N; height[RANK[i++]]=k)
	for(k= k? k-1: k, j=SA[RANK[i]-1]; a[i+k]== a[j+k]; k++);
}


void solve()
{
	int pos=0,top=0;
	LL del=0;
	for(int i=1;i<=N;i++)
	{
		int p=pos;
		while(top&&stk[top].val>height[i]) top--; 
		if(top) p=stk[top].id; 
		f[i]=f[p]+(LL)(i-p)*height[i];
		del+=f[i];
		if(!height[i]) pos=i; 
		stk[++top]=(data){i,height[i]};
	}
	
	cout<<(LL)(N-1)*(N+1)*N/2-del*2;
}

int main()
{
	init();
	suffix();
	solve();
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/81329125