1. 程式人生 > >[bzoj3879]SvT_字尾陣列_RMQ_單調棧

[bzoj3879]SvT_字尾陣列_RMQ_單調棧

SvT bzoj-3879

題目大意:給定一個字串。每次詢問給定$t$個位置,求兩兩位置開頭的字尾的$LCP$之和。

註釋:$1\le length\le 5\cdot 10^5$,$\sum t\le 3\cdot 10^6$。


想法

不難想到構建字尾陣列。

進而我們的問題就轉化成了給定序列上一些位置求這些位置兩兩之間區間最小值的和。

對$ht$陣列建立$ST$表。

接下來的過程可以用單調棧維護。

Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 500010
using namespace std; typedef long long ll;
int n,m,wa[N],wb[N],wv[N],sa[N],height[N],rank[N],r[N],Ws[N];
char ch[N];
int f[21][N],L[N],vis[N],s[N],g[N];
int v[3000050],Q[3000050];
ll dp[N];
inline char nc()
{
	static char buf[100000],*p1,*p2;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int rd()
{
	int x=0; char c=nc();
	while(c<'0'||c>'9') c=nc();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=nc();
	return x;
}
inline int rc()
{
	char c=nc();
	while(c<'a'||c>'z') c=nc();
	return (int)c;
}

void build_sa()
{
	m=27;
	int i,j,p,*x=wa,*y=wb,*t;
	for(i=0;i<m;i++) Ws[i]=0;
	for(i=0;i<n;i++) Ws[x[i]=r[i]]++;
	for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
	for(i=n-1;i>=0;i--) sa[--Ws[x[i]]]=i;
	for(p=j=1;p<n;j<<=1,m=p)
	{
		for(p=0,i=n-j;i<n;i++) y[p++]=i;
		for(i=0;i<n;i++) if(sa[i]-j>=0) y[p++]=sa[i]-j;
		for(i=0;i<n;i++) wv[i]=x[y[i]];
		for(i=0;i<m;i++) Ws[i]=0;
		for(i=0;i<n;i++) Ws[wv[i]]++;
		for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
		for(i=n-1;i>=0;i--) sa[--Ws[wv[i]]]=y[i];
		for(t=x,x=y,y=t,i=p=1,x[sa[0]]=0;i<n;i++)
		{
			if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p-1;
			else x[sa[i]]=p++;
		}
	}
	for(i=1;i<n;i++) rank[sa[i]]=i;
	for(i=p=0;i<n-1;height[rank[i++]]=p)
		for(p?p--:0,j=sa[rank[i]-1];r[i+p]==r[j+p];p++);
}
int get_min(int l,int r)
{
	int len=L[r-l+1];
	return min(f[len][l],f[len][r-(1<<len)+1]);
}
void ST()
{
	int i,j;
	for(i=2;i<=n;i++) L[i]=L[i>>1]+1;
	for(i=1;i<=n;i++) f[0][i]=height[i];
	for(i=1;(1<<i)<=n;i++)
	{
		for(j=1;j+(1<<i)-1<=n;j++) f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
	}
}
bool cmp(int x,int y)
{
	return rank[x]<rank[y];
}
int main()
{
	int T;
	n=rd(); T=rd();
	int i;
	for(i=0;i<n;i++) r[i]=rc()-'a'+1;
	r[n++]=0;
	int tot=0;
	build_sa(); n--; ST();
	while(T--)
	{
		tot++;
		int t=0;
		v[0]=rd();
		int j;
		for(j=1;j<=v[0];j++)
		{
			v[j]=rd();
			v[j]--;
			if(vis[v[j]]==tot) {j--; v[0]--;}
			vis[v[j]]=tot;
		}
		sort(v+1,v+v[0]+1,cmp);
		for(j=1;j<v[0];j++)
		{
			g[j]=get_min(rank[v[j]]+1,rank[v[j+1]]);
		}
		t=1; Q[1]=0;
		long long ans=0;
		for(j=1;j<v[0];j++)
		{
			while(t&&g[Q[t]]>g[j]) t--;
			dp[j]=dp[Q[t]]+1ll*(j-Q[t])*g[j];
			ans+=dp[j];
			Q[++t]=j;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

小結:字尾陣列真好玩。