題目鏈接:點(diǎn)擊查看
題目大意:給出一個(gè)長(zhǎng)度為 n 的字符串,再給出 m 次詢問(wèn),每次詢問(wèn)需要回答區(qū)間 [ l , r ] 內(nèi)有多少個(gè)本質(zhì)不同的字符串
題目分析:首先簡(jiǎn)化模型,回顧一下如何求解 “區(qū)間內(nèi)有多少個(gè)不同的數(shù)” :SPOJ - DQUERY
求解上面那道題目的方法可謂是五花八門(mén),我們采用其中一種離線的方式:先將所有的詢問(wèn)儲(chǔ)存下來(lái),按照右端點(diǎn) r 排序后,每次對(duì)于重復(fù)出現(xiàn)的數(shù)字,用線段樹(shù)維護(hù)其最后一次出現(xiàn)的位置,那么答案就是 [ l , r ] 中元素的個(gè)數(shù)了
對(duì)于這個(gè)題目而言,將每個(gè)本質(zhì)不同的字符串視為一個(gè)連續(xù)的區(qū)間 [ l , r?],我們只需要維護(hù)左端點(diǎn)最后一次出現(xiàn)的位置即可
因?yàn)樾枰烂總€(gè)子串最后一次出現(xiàn)的位置,所以我們選擇對(duì)字符串構(gòu)造后綴自動(dòng)機(jī),對(duì)于某個(gè)右端點(diǎn) r 來(lái)說(shuō),對(duì)應(yīng)到 SAM 上,從最長(zhǎng)的那個(gè)前綴 [ 1 , r ] 對(duì)應(yīng)的節(jié)點(diǎn)開(kāi)始,沿著 parent 樹(shù)到根節(jié)點(diǎn)的這條路徑上,都是以 r 為右端點(diǎn)的子串,設(shè) pre[ i ] 為節(jié)點(diǎn) i 上一次出現(xiàn)時(shí)的右端點(diǎn)位置,我們只需要暴跳 father,每次將之前出現(xiàn)過(guò)的位置,在線段樹(shù)上區(qū)間更新成 -1 (因?yàn)閷?duì)于每個(gè)節(jié)點(diǎn)來(lái)說(shuō),代表的都是一段連續(xù)的后綴,所以可以用到線段樹(shù)的區(qū)間更新),最后再將 [ 1 , r ] , [ 2 , r ] ... [ r , r ] 這 r 個(gè)后綴所代表的子串的左端點(diǎn),即 [ 1 , r ] 在線段樹(shù)上 +1 就好了
不過(guò)問(wèn)題是,這樣暴跳 father 的時(shí)間復(fù)雜度是不正確的,直接這樣實(shí)現(xiàn)在本題中只能得到 80 分,有一個(gè)測(cè)試點(diǎn)會(huì)超時(shí)
考慮優(yōu)化,因?yàn)槊看芜x擇一條鏈,自下而上去更新,其本質(zhì)就是:
令這條鏈每個(gè)節(jié)點(diǎn)相應(yīng)的位置在線段樹(shù)上區(qū)間更新 令每個(gè)節(jié)點(diǎn)都被端點(diǎn) r 覆蓋(也就是將 pre 都標(biāo)記為 r)
這個(gè)過(guò)程其實(shí)就是 LCT 中的 access 函數(shù)的操作,這樣每次更新至多需要更新?logn 個(gè) splay,套上線段樹(shù)就是 log^2n,均攤下來(lái)就是 nlog^2n 了
因?yàn)樵?SAM 自底向上的一條路徑上,其中任意連續(xù)的一段,所能表示的后綴也是連續(xù)的,所以在 LCT 的 splay 上只需要維護(hù)一下相應(yīng)重鏈上最短的子串長(zhǎng)度就好了
因?yàn)樵诒绢}中用不到 LCT 的 link、cut 等操作,所以將相應(yīng)的一些基本函數(shù)都刪掉了,只保留了與 access 函數(shù)有關(guān)的一些基本函數(shù)
總時(shí)間復(fù)雜度是 O( nlog^2n + mlogn )
代碼: ?
//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=1e6+100;char s[N];vector<pair<int,int>>q[N];LL ans[N];namespace Seg
{struct Node{int l,r,len;LL sum,lazy;}tree[N<<2];void pushup(int k){tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;}void pushdown(int k){if(tree[k].lazy){LL lz=tree[k].lazy;tree[k].lazy=0;tree[k<<1].sum+=tree[k<<1].len*lz;tree[k<<1|1].sum+=tree[k<<1|1].len*lz;tree[k<<1].lazy+=lz;tree[k<<1|1].lazy+=lz;}}void build(int k,int l,int r){tree[k].l=l;tree[k].r=r;tree[k].len=r-l+1;tree[k].sum=tree[k].lazy=0;if(l==r)return;int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);}void update(int k,int l,int r,LL val){if(tree[k].l>r||tree[k].r<l)return;if(tree[k].r<=r&&tree[k].l>=l){tree[k].sum+=tree[k].len*val;tree[k].lazy+=val;return;}pushdown(k);update(k<<1,l,r,val);update(k<<1|1,l,r,val);pushup(k);}LL query(int k,int l,int r){if(tree[k].l>r||tree[k].r<l)return 0;if(tree[k].l>=l&&tree[k].r<=r)return tree[k].sum;pushdown(k);return query(k<<1,l,r)+query(k<<1|1,l,r);}
}namespace SAM
{int tot,last,pos[N],pre[N];struct Node{int ch[26];int fa,len;}st[N<<1];inline int newnode(){tot++;for(int i=0;i<26;i++)st[tot].ch[i]=0;st[tot].fa=st[tot].len=0;return tot;}void add(int x){int p=last,np=last=newnode();st[np].len=st[p].len+1;while(p&&!st[p].ch[x])st[p].ch[x]=np,p=st[p].fa;if(!p)st[np].fa=1;else{int q=st[p].ch[x];if(st[p].len+1==st[q].len)st[np].fa=q;else{int nq=newnode();st[nq]=st[q]; st[nq].len=st[p].len+1;st[q].fa=st[np].fa=nq;while(p&&st[p].ch[x]==q)st[p].ch[x]=nq,p=st[p].fa;//向上把所有q都替換成nq}}}void init(){last=1;tot=0;newnode();}void build(){init();int len=strlen(s+1);for(int i=1;i<=len;i++){add(s[i]-'a');pos[i]=last;}}
}namespace LCT
{int fa[N],ch[N][2],pre[N],min_len[N],len[N],Stack[N],lazy[N],top;bool son(int x)//判斷點(diǎn)x是父節(jié)點(diǎn)的左兒子還是右兒子 {return x==ch[fa[x]][1];}bool isroot(int x)//判斷一個(gè)點(diǎn)是不是根節(jié)點(diǎn)(當(dāng)前splay的根節(jié)點(diǎn)){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}void pushup(int x)//上傳(維護(hù)信息){min_len[x]=min(len[x],min(min_len[ch[x][0]],min_len[ch[x][1]]));}void update_lazy(int x,int lz){pre[x]=lazy[x]=lz;}void pushdown(int x)//下傳(更新反轉(zhuǎn)標(biāo)記用){if(lazy[x]){update_lazy(ch[x][0],lazy[x]);update_lazy(ch[x][1],lazy[x]);lazy[x]=0;}}void rotate(int x)//splay的旋轉(zhuǎn) {int y=fa[x],z=fa[y],c=son(x);ch[y][c]=ch[x][c^1];if(ch[y][c]) fa[ch[y][c]]=y;fa[x]=z;if(!isroot(y))ch[z][son(y)]=x;ch[x][c^1]=y;fa[y]=x;pushup(y);}void splay(int x)//將x轉(zhuǎn)到根節(jié)點(diǎn) {Stack[top=1]=x;for (int i=x;!isroot(i);i=fa[i])Stack[++top]=fa[i];while (top) pushdown(Stack[top--]);for (int y=fa[x];!isroot(x);rotate(x),y=fa[x])if (!isroot(y)) son(x)^son(y)?rotate(x):rotate(y);pushup(x);}void access(int x,int id)//把x節(jié)點(diǎn)到x所在樹(shù)(連通塊)的根節(jié)點(diǎn)之間的路徑全部變成重路徑{int y;for(y=0;x;y=x,x=fa[x]){splay(x),ch[x][1]=y,pushup(x);if(pre[x])Seg::update(1,pre[x]-SAM::st[x].len+1,pre[x]-min_len[x]+1,-1);}splay(y);update_lazy(y,id);Seg::update(1,1,id,1);}void build(){min_len[0]=inf;for(int i=1;i<=SAM::tot;i++){fa[i]=SAM::st[i].fa;len[i]=min_len[i]=SAM::st[SAM::st[i].fa].len+1;lazy[i]=ch[i][0]=ch[i][1]=pre[i]=0;}}
};int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);scanf("%s",s+1);int n=strlen(s+1);SAM::build();LCT::build();Seg::build(1,1,n);int m;scanf("%d",&m);for(int i=1;i<=m;i++){int l,r;scanf("%d%d",&l,&r);q[r].emplace_back(i,l);}for(int i=1;i<=n;i++){LCT::access(SAM::pos[i],i);for(auto it:q[i])ans[it.first]=Seg::query(1,it.second,i);}for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);return 0;
}
?
總結(jié)
以上是生活随笔 為你收集整理的洛谷 - P6292 区间本质不同子串个数(SAM+LCT+线段树) 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。