【題解】[HEOI2016/TJOI2016]字串
阿新 • • 發佈:2021-08-23
\(\text{Solution:}\)
記錄一下這題獲得的啟發。
- 最長公共字首/字尾一類問題,具有可二分性。
考慮二分一個答案,然後看如何檢驗。
如果答案是 \(mid,\) 那麼對應的串應該就是 \(s[c\cdots c+mid-1],\) 我們發現:它是字首 \(s[1\cdots c+mid-1]\) 的字尾。
- 通過字首把子串轉化成字尾
假定我們找到了一個點,它代表的串包含了 \(s[c\cdots c+mid-1],\) 如何檢視是不是滿足有一個 \(s[a\cdots b]\) 的子串是其字首呢?
我們發現:它是區間內的 所有子串,也就是說,只要出現過即可
那麼就自然想到用 endpos 來判斷。
考慮一下,當前答案是 \(mid,\) 如果要滿足,其結束位置至少應該在 \(a+mid-1.\) 也就是說,我們需要查詢這個點的 endpos 是否包含了區間 \([a+mid-1,b]\) 中的一個點。
這就需要線段樹維護 endpos 的 trick 了。
接下來思考如何定位節點。考慮我們的 parent 樹實際上是一棵 字首樹 ,它滿足:每一條葉節點到根的路徑對應一個字首,同時,每一個非葉子節點到根的路徑都對應了某字首的字尾。
也就是說,我們定位到一個點後,向上跳父親,實際就是在不斷找字尾的過程。
那麼我們前文所述,把它看成了字首 \(s[1\cdots c+mid-1]\)
這簡單,從根開始依次匹配,匹配到的點對應的字首就是答案。
那怎麼往上跳定位呢?考慮經典技巧:倍增 。
考慮如何判斷是不是跳到了對應節點:它一定滿足,自身的 \(len\ge mid,\) 其父親的 \(len<mid.\) 而 \(len\) 自下向上又具有單調性。
考慮倍增跳父親即可。剩下的就是線段樹上查詢了。複雜度 \(O(n\log^2 n).\)
#include<bits/stdc++.h> using namespace std; const int N=4e6+10; const int SN=2e5+10; int n,m; char s[SN]; int sufpos[SN]; inline int Min(int x,int y){return x<y?x:y;} inline int Max(int x,int y){return x>y?x:y;} namespace SGT{ int ls[N],rs[N],node; void change(int &x,const int &L,const int &R,const int &pos){ if(!x)x=++node; if(L==R)return; int mid=(L+R)>>1; if(pos<=mid)change(ls[x],L,mid,pos); else change(rs[x],mid+1,R,pos); } int merge(const int &x,const int &y){ if(!x||!y)return x|y; int p=++node; ls[p]=merge(ls[x],ls[y]); rs[p]=merge(rs[x],rs[y]); return p; } bool query(const int &x,const int &L,const int &R,const int &l,const int &r){ if(!x)return 0; if(L>=l&&R<=r)return 1; int mid=(L+R)>>1; int res=0; if(l<=mid)res=query(ls[x],L,mid,l,r); if(mid<r)res|=query(rs[x],mid+1,R,l,r); return res; } } using namespace SGT; namespace SAM{ int len[SN],ch[SN][26],pa[SN],rt[SN],last=1,tot=1; vector<int>G[N]; int f[N][21]; void insert(const int &c){ int p=last; int np=++tot; last=tot;len[np]=len[p]+1; for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np; if(!p)pa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1)pa[np]=q; else{ int nq=++tot; len[nq]=len[p]+1; memcpy(ch[nq],ch[q],sizeof ch[q]); pa[nq]=pa[q];pa[q]=pa[np]=nq; for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq; } } } void dfs(int x){ for(auto v:G[x]){ f[v][0]=x; for(int i=1;i<21;++i)f[v][i]=f[f[v][i-1]][i-1]; dfs(v); rt[x]=merge(rt[x],rt[v]); } } void Build(){ for(int i=2;i<=tot;++i)G[pa[i]].push_back(i); dfs(1); } } using namespace SAM; bool check(int mid,int a,int b,int c){ //endpos in [a+mid-1,b] //Let us find the position of c. int posc=sufpos[c+mid-1]; //the sequence is [c,c+mid-1] for(int i=20;~i;--i)if(len[f[posc][i]]>=mid)posc=f[posc][i]; return query(rt[posc],1,n,a+mid-1,b); } int main(){ scanf("%d%d",&n,&m); scanf("%s",s+1); for(int i=1;i<=n;++i)insert(s[i]-'a'); for(int i=1,now=1;i<=n;++i){ int v=s[i]-'a'; now=ch[now][v]; sufpos[i]=now; change(rt[now],1,n,i); } Build(); while(m--){ int a,b,c,d; scanf("%d%d%d%d",&a,&b,&c,&d); int len1=b-a+1; int len2=d-c+1; int l=1,r=Min(len1,len2); int Ans=-1; while(l<=r){ int mid=(l+r)>>1; if(check(mid,a,b,c))l=mid+1,Ans=mid; else r=mid-1; } printf("%d\n",Ans); } return 0; }