事情的相似度(SAM+LCT+樹狀數組)

 

 

題解

蒟蒻的第一道字符串大題

此題的題意是求編號爲一段區間的前綴的最長公共後綴的長度

而這個最長公共後綴是可以超過這個區間限制的(被坑了好久。。。)

 

那麼這題就比較有思路了

我們可以考慮一下暴力

首先,我們對於每一個前綴[1,i],算出所有的前綴[1,1~i-1]與它的最長公共後綴的大小,存到一個vector裏面

查詢的時候就只查[l,r]中每個點x的vector裏面的[l,x-1]的答案,取最大值即可

這樣做是O(Qn^2)的

考慮最長公共後綴的本質,不難聯想到SAM

先對原串建出SAM,在SAM上,兩個點在fail樹上的LCA的len值表示了這兩個點的最長公共後綴的長度

一個前綴的所有後綴表示在fail樹上就是該前綴的對應點x到根節點的一條鏈

再開一下腦洞,聯想到[LNOI2014]LCA那道題

考慮離線詢問,對於右端點 r 處理出所有左端點 l 的答案

我們把所有的1~r-1的前綴對應點x到根的路徑依次做一個標記覆蓋

(這其實是在貪心,因爲越靠後的前綴越可能影響更多的查詢)

然後我們查詢:在前綴r的對應點xr到根的路徑上,覆蓋時間大於等於 l 的len值最大的點

直接做這個問題會非常麻煩,因爲max不能前綴相減,你還需要用二維線段樹來可持久化樹鏈剖分

然而這並沒有發揮我們離線的優勢

考慮再維護一個樹狀數組,來記錄當前r對應的每個左端點的答案

利用LCT來維護鏈覆蓋與標記的下傳,在Access的過程中更新樹狀數組

每次都只更新跳鏈時跳向的那一個點的權值

這時有人就會問,萬一中間有一個點的位置更靠後,而這種做法沒有更新到這個點怎麼辦?

其實你Access之前,你到根需要跳多少次鏈,你就經過了多少種不同的鏈標記

如圖,鏈3在Access的時候,會經過兩種不同的標記,兩種標記都會被更新到

所以不用擔心漏掉某個點沒更新的情況

注意,標記下傳之後不要清零,因爲標記剛打上去會被splay前的pdpath傳到子樹中

下一次直接訪問到該點的時候就沒有標記來更新了,這樣可以減小常數

如果覺得難以理解,可以在pushdown的時候更新樹狀數組,這樣做應該是可以清零標記的,只不過常數會比較大

代碼:O(nlog^2n)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define N 200005
int n,m;
int pos[N],ans[N];
namespace SAM{
	int ch[N][2],fa[N],len[N],las,tot;
	void extend(int x){
		int p,np,q,nq;
		p=las;np=++tot;
		len[np]=len[p]+1;
		for(;p&&!ch[p][x];p=fa[p])
			ch[p][x]=np;
		if(!p)fa[np]=1;
		else{
			q=ch[p][x];
			if(len[q]==len[p]+1)fa[np]=q;
			else{
				nq=++tot;
				len[nq]=len[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];
				for(;p&&ch[p][x]==q;p=fa[p])ch[p][x]=nq;
				fa[q]=fa[np]=nq;
			}
		}
		las=np;
	}
}//----SAM----
int tra[N];
void upd(int x,int k)
{
	x=n-x+1;
	while(x<=n){
		tra[x]=max(tra[x],k);
		x+=(x&-x);
	}
}
int qry(int x)
{
	int ret=0;x=n-x+1;
	while(x){
		ret=max(ret,tra[x]);
		x-=(x&-x);
	}
	return ret;
}
namespace LCT{
	int ch[N][2],fa[N],la[N];
	bool pdc(int x){return ch[fa[x]][1]==x;}
	bool nrt(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
	void rot(int x){
		int y=fa[x],z=fa[y];bool flg=pdc(x);
		if(nrt(y))ch[z][pdc(y)]=x;// !!!
		if(ch[y][flg]=ch[x][flg^1])
			fa[ch[y][flg]]=y;
		ch[x][flg^1]=y;
		fa[y]=x;fa[x]=z;
	}
	void pushdown(int x){
		if(la[x]){
			la[ch[x][0]]=la[ch[x][1]]=la[x];
			//la[x]=0;  // !!!
		}
	}
	void pdpath(int x){if(nrt(x))pdpath(fa[x]);pushdown(x);}
	void splay(int x){
		for(pdpath(x);nrt(x);rot(x))
			if(nrt(fa[x]))rot(pdc(fa[x])==pdc(x)?fa[x]:x);
	}
	void acc(int x,int k){
		for(int i=0;x;x=fa[x]){
			splay(x);
			if(la[x])upd(la[x],SAM::len[x]);
			la[x]=k;
			ch[x][1]=i;
			i=x;
		}
	}
}//----LCT----
vector<pair<int,int> > G[N];
int main()
{
	//freopen("A1.in","r",stdin);
	SAM::tot=SAM::las=1;
	int i,x,l,r;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%1d",&x);SAM::extend(x);
		pos[i]=SAM::las;
	}
	for(i=1;i<=SAM::tot;i++)LCT::fa[i]=SAM::fa[i];
	for(i=1;i<=m;i++){
		scanf("%d%d",&l,&r);
		G[r].push_back(make_pair(l,i));
	}
	for(i=1;i<=n;i++){
		LCT::acc(pos[i],i);
		for(int j=0;j<(int)G[i].size();j++)
			ans[G[i][j].second]=qry(G[i][j].first);
	}
	for(i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章