1. 程式人生 > 其它 >AB213 F Common Prefixes 題解

AB213 F Common Prefixes 題解

AB213 F Common Prefixes 題解

題面

定義 \(f(X,Y)\) 為字串 \(X,Y\) 的公共字首長度。

給定長度為 \(N\) 的字串 \(S\),設 \(S_i\) 表示從第 \(i\) 個字元開始的 \(S\) 的字尾。

計算出:對於 \(k=1,2,...,N\),\(f(S_k,S_1)+f(S_k,S_2)+f(S_k,S_3)+...+f(S_k,S_N)\) 的值。

輸入格式

第一行一個整數 \(N(1≤N≤106)\)

第二行為長度 \(N\) 的字串 \(S\)

輸出格式

輸出 \(N\) 行,第 \(k\) 行,表示 \(f(S_k,S_1)+f(S_k,S_2)+f(S_k,S_3)+...+f(S_k,S_N)\)

的值。

樣例輸入

3
abb

樣例輸出

3
3
2

樣例解釋

\(S1=abb,S2=bb,S3=b\)

\(For\ k=1\ f(S1,S1)+f(S1,S2)+f(S1,S3)=3+0+0=3\)

\(For\ k=2\ f(S2,S1)+f(S2,S2)+f(S2,S3)=0+2+1=3\)

\(For\ k=3\ f(S3,S1)+f(S3,S2)+f(S3,S3)=0+1+1=2\)

解題

演算法:SA+LCP+單調棧

前置知識:字尾陣列學習筆記

顯然,\(f\)\(LCP\) 的含義是相同的

我們知道,\(LCP(i,j)=\min_{i\leq k\leq j-1}\{LCP(k,k+1)\}\)

所以,問題被轉化成這樣:

Problem:

給定一個長為 \(n\) 的序列 \(a_i\),

\(g(i,j)=\min_{i\leq k\leq j-1}\{a_k\}\)

對於每一個 \(k=1\dots n\), 求值: \(\sum_{i=1}^n g(i,k)\)

我們考慮分開求,分成 \(\sum_{i=1}^{k-1}g(i,k)+g(k,k)+\sum_{i=k+1}^ng(i,k)\)

顯然,第一個和第二個本質相同,中間那個就是 \(Suf(k)\) 的長度。

\(pre_k=\sum_{i=1}^{k-1}g(i,k)\) ,觀察:

\[\begin{align} pre_k&=g(1,k)+g(2,k)+g(3,k)+\dots+g(k-1,k)\\ pre_{k+1}&=g(1,k+1)+g(2,k+1)+g(3,k+1)+\dots+g(k-1,k)+a_{k}\\ &=\min(g(1,k),a_k)+\min(g(2,k),a_k)+\dots +\min(g(k-1,k),a_k)+a_k \end{align} \]

可以發現,二者之間存在遞推關係

如何處理取 \(\min\) 操作?

考慮 \(a_k\) 的貢獻範圍:\((前面第一個大於 a_k的位置,k+1]\)

所以,我們可以採用 單調棧 (其實程式碼比文字更好懂)

另外,有一道相似的題目:[AHOI2013]差異 - 洛谷 ,其中也有單調棧的思想

程式碼

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N = 1e6+5;

int n,m,top;
int tax[N],rk[N],sa[N],tp[N],h[N];
int pre[N],suf[N],stk[N];
char s[N];

void radix_sort(){
	for(int i=1;i<=m;++i)tax[i]=0;
	for(int i=1;i<=n;++i)tax[rk[i]]++; 
	for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
	for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
}
void suffix_sort(){
	m=75;
	for(int i=1;i<=n;++i)
		rk[i]=s[i]-'a'+1,tp[i]=i;
	radix_sort();
	for(int w=1,p=0;w<n;m=p,w<<=1,p=0){
		for(int i=1;i<=w;++i)tp[++p]=n-w+i;
		for(int i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
		radix_sort();swap(tp,rk);
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;++i)
			rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
	}
}
void geth(){
	int j,k=0;
	for(int i=1;i<=n;++i){
		if(k)--k;
		int j=sa[rk[i]-1];
		while(s[i+k]==s[j+k])++k;
		h[rk[i]]=k;
	}
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;cin>>(s+1);
	suffix_sort();geth();
	stk[top=0]=1;//維護一個單調上升的棧
	for(int i=2;i<=n;++i){
		pre[i]=pre[i-1];
		while(top&&h[i]<h[stk[top]])
			pre[i]-=(stk[top]-stk[top-1])*h[stk[top--]];//減去之前的貢獻
		pre[i]+=(i-stk[top])*h[i];
		stk[++top]=i;
	}
	stk[top=0]=n+1;
	for(int i=n;i>=2;--i){
		suf[i]=suf[i+1];
		while(top&&h[i]<h[stk[top]])
			suf[i]-=(stk[top-1]-stk[top])*h[stk[top--]];
		suf[i]+=(stk[top]-i)*h[i];
		stk[++top]=i;
	}
	for(int i=1;i<=n;++i)
		cout<<pre[rk[i]]+suf[rk[i]+1]+n-i+1<<"\n";
	return 0;
}