【SAM+線段樹合併】LGP4770 [NOI2018]你的名字
阿新 • • 發佈:2019-01-05
【題目】
原題地址
給定一個字串
,多組詢問給定字串
以及兩個數字
。求
中有多少個子串
滿足:
的任意一個子串沒有在
中出現過。
【解題思路】
加深對
的理解。
既然是字串題,我們首先對
和
分別建
。
考慮
的情況。
設
為
能匹配
的最長字尾為
(若
則
沒有在
中出現過)。那麼這個我們在
上一路往下跑,匹配不了就往
跳,這樣就可以簡單處理出來,有點類似雙指標。
設
中節點
的
集合包含的字串最大長度為
,字串第一次出現的位置為
(這個節點
集中的每個串
都是一樣的,因為是字尾包含關係,終點等價)。我們列舉
中的每個節點,考慮每條
到
邊的貢獻,那麼:
這個式子的意思就是對於每個節點,不屬於
的子串的總個數為當前節點所代表的集合字串個數減去與
有匹配的字串個數。
於是現在實際上總的問題就是求這個
。
我們可以對於
的
每個節點按
排序,每個節點建線段樹表示出現位置,做線段樹合併。那麼每次我們求
,往下擴充套件的時候,我麼只需要判斷這個節點線段樹中
是否出現過即可。其中
表示當前匹配了
,出現了即表示字串出現在
內。
複雜度 ,我們取 是 級別吧。
【參考程式碼】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10,M=N*20;
int rt[N],b[N],c[N],lim[N];
char s[N];
ll ans;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct Segment
{
int sz,ls[M],rs[M];
void insert(int &x,int l,int r,int p)
{
if(!x) x=++sz;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) insert(ls[x],l,mid,p);
else insert(rs[x],mid+1,r,p);
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
int z=++sz;
ls[z]=merge(ls[x],ls[y]);rs[z]=merge(rs[x],rs[y]);
return z;
}
bool query(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(L<=l && r<=R) return 1;
int mid=(l+r)>>1;bool res=0;
if(L<=mid) res|=query(ls[x],l,mid,L,R);
if(R>mid) res|=query(rs[x],mid+1,r,L,R);
return res;
}
}tr;
struct SAM
{
int sz,tot,las,n,mx[N],fa[N],pos[N],ch[N][26];
void extend(int x,int c)
{
int p