題解
蒟蒻的第一道字符串大題
此題的題意是求編號爲一段區間的前綴的最長公共後綴的長度
而這個最長公共後綴是可以超過這個區間限制的(被坑了好久。。。)
那麼這題就比較有思路了
我們可以考慮一下暴力
首先,我們對於每一個前綴[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]);
}