洛谷 - P6292 區間本質不同子串個數(SAM+LCT+線段樹)
題目連結:點選檢視
題目大意:給出一個長度為 n 的字串,再給出 m 次詢問,每次詢問需要回答區間 [ l , r ] 內有多少個本質不同的字串
題目分析:首先簡化模型,回顧一下如何求解 “區間內有多少個不同的數” :SPOJ - DQUERY
求解上面那道題目的方法可謂是五花八門,我們採用其中一種離線的方式:先將所有的詢問儲存下來,按照右端點 r 排序後,每次對於重複出現的數字,用線段樹維護其最後一次出現的位置,那麼答案就是 [ l , r ] 中元素的個數了
對於這個題目而言,將每個本質不同的字串視為一個連續的區間 [ l , r ],我們只需要維護左端點最後一次出現的位置即可
因為需要知道每個子串最後一次出現的位置,所以我們選擇對字串構造字尾自動機,對於某個右端點 r 來說,對應到 SAM 上,從最長的那個字首 [ 1 , r ] 對應的節點開始,沿著 parent 樹到根節點的這條路徑上,都是以 r 為右端點的子串,設 pre[ i ] 為節點 i 上一次出現時的右端點位置,我們只需要暴跳 father,每次將之前出現過的位置,線上段樹上區間更新成 -1 (因為對於每個節點來說,代表的都是一段連續的字尾,所以可以用到線段樹的區間更新),最後再將 [ 1 , r ] , [ 2 , r ] ... [ r , r ] 這 r 個字尾所代表的子串的左端點,即 [ 1 , r ] 線上段樹上 +1 就好了
不過問題是,這樣暴跳 father 的時間複雜度是不正確的,直接這樣實現在本題中只能得到 80 分,有一個測試點會超時
考慮優化,因為每次選擇一條鏈,自下而上去更新,其本質就是:
- 令這條鏈每個節點相應的位置線上段樹上區間更新
- 令每個節點都被端點 r 覆蓋(也就是將 pre 都標記為 r)
這個過程其實就是 LCT 中的 access 函式的操作,這樣每次更新至多需要更新 logn 個 splay,套上線段樹就是 log^2n,均攤下來就是 nlog^2n 了
因為在 SAM 自底向上的一條路徑上,其中任意連續的一段,所能表示的字尾也是連續的,所以在 LCT 的 splay 上只需要維護一下相應重鏈上最短的子串長度就好了
因為在本題中用不到 LCT 的 link、cut 等操作,所以將相應的一些基本函式都刪掉了,只保留了與 access 函式有關的一些基本函式
總時間複雜度是 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)//判斷點x是父節點的左兒子還是右兒子
{
return x==ch[fa[x]][1];
}
bool isroot(int x)//判斷一個點是不是根節點(當前splay的根節點)
{
return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void pushup(int x)//上傳(維護資訊)
{
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)//下傳(更新反轉標記用)
{
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的旋轉
{
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轉到根節點
{
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節點到x所在樹(連通塊)的根節點之間的路徑全部變成重路徑
{
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;
}
相關文章
- 【線段樹提高】51nod &&洛谷
- 洛谷P1712 [NOI2016]區間 尺取法+線段樹+離散化
- 洛谷題單指南-線段樹-P3373 【模板】線段樹 2
- 洛谷題單指南-線段樹-P1471 方差
- 可持久化線段————主席樹(洛谷p3834)持久化
- BZOJ 2752 [HAOI2012]高速公路(road):線段樹【維護區間內子串和】
- 線段樹維護區間等差數列
- 求區間不同數的個數【主席樹求解】
- 洛谷P4425 [HNOI/AHOI2018]轉盤(線段樹)
- 線段樹 區間乘法加法混合
- 洛谷P4069 [SDOI2016]遊戲(李超線段樹)遊戲
- 求區間不同數的個數【樹狀陣列求解】陣列
- 洛谷 P3919 可持久化線段樹 1 之主席樹模板(初級)持久化
- 【Leetcode每日一題】327. 區間和的個數(線段樹/樹狀陣列)LeetCode每日一題陣列
- 線段樹(3)——區間操作疊加
- HDU 2795 Billboard(線段樹 區間最大)
- 洛谷題單指南-線段樹-P1253 扶蘇的問題
- 洛谷P3586 [POI2015]LOG(貪心 權值線段樹)
- 芻議線段樹 2 (區間修改,區間查詢)
- POJ 3468 【區間修改+區間查詢 樹狀陣列 | 線段樹 | 分塊】陣列
- HDU 3397 Sequence operation(線段樹區間染色加區間合併)
- 區間k小值(可持久化線段樹)持久化
- HDU 1754 I Hate It (線段樹 區間最值)
- 線段樹科技合訂本
- 洛谷P2178 [NOI2015]品酒大會(字尾自動機 線段樹)
- 洛谷死亡時間
- POJ 3468 A Simple Problem with Integers (線段樹 區間共加)
- HDU1698 Just a Hook【線段樹基礎:區間修改+區間查詢】Hook
- 洛谷-P1250 種樹
- 洛谷P1087 FBI樹
- HDU 4027 Can you answer these queries? (線段樹 區間開方)
- 區間演算法題用線段樹可以秒解?演算法
- HDU 1671 字典樹(判斷是否有一個串是另一個串的子串)。
- POJ 2528 Mayor's posters (線段樹 區間更新+離散化)
- 洛谷P3383 【模板】線性篩素數
- 【洛谷】【分支】月份天數
- 【知識點】淺入線段樹與區間最值問題
- 洛谷P3369 普通平衡樹(Splay)