bzoj5417&&luogu4770你的名字 字尾自動機+線段樹合併
bzoj5417&&luogu4770你的名字
分析
題目大意:
給定一個模板串
,每次給定一個字串
和
,求
中有多少個本質不同的子串無法匹配
的子串
首先肯定考慮的是
的情況。
分析
的每一個字首
肯定存在一個分界點
,使得這個字首
的字尾有
可以匹配
無法匹配
並且這個
是單調遞增的,我們令
。
由於題目中要求的是本質不同的子串,所以我們必須對
建立字尾自動機,對於每一個
上的節點,假設其
集合中最先出現的位置為
,那麼這個節點
的貢獻就是
這個式子的意義是,考慮字尾自動機的
樹等價於把原串的所有字首逆序插入
後壓縮,
就是這個節點到根節點的所代表的字串長度,那麼這個節點就壓縮了串
中的資訊,而
是表示從某個位置往前沒有貢獻的最後一個位置。所以產生貢獻的長度就是
現在考慮如何求
按順序考慮
的每一個字首,由於
的單調性質,可以採用類似雙指標的方式,隨著字首的挪動,去找到合法的
。
由於是匹配
的子串,所以對
另外建立一顆字尾自動機,我們只需要在
上進行匹配。如果新走了某字元
,如果存在
,那麼就往下走,否則的話就移動
,跳
鏈即可。
現在考慮加上
的限制。
實際上等價於當前節點
的
集合記憶體在某個位置
因為你要從當前位置
匹配長度為
的串,也就是
接下來我們只要求查詢
字尾自動機上某個節點
的
集合中是否含有在某個區間內的數。
這個東西採用線段樹合併或者主席樹維護子樹
即可。
因為某個節點的
集合就是其
樹上兒子的
集合的並。
程式碼
採用線段樹合併
#include<bits/stdc++.h>
#define re(x) std::memset(x, 0, sizeof(x))
const int N = 1e6 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int ps[N], rt[N], c[N], sa[N], lim[N], n;
struct Segment {
int ls[N * 20], rs[N * 20], top;
void Modify(int &p, int L, int R, int x) {
p = ++top; if(L == R) return ; int m = L + R >> 1;
x <= m ? Modify(ls[p], L, m, x) : Modify(rs[p], m + 1, R, x);
}
int Merge(int u, int v) {
if(!u || !v) return u | v;
int np = ++top;
ls[np] = Merge(ls[u], ls[v]);
rs[np] = Merge(rs[u], rs[v]);
return np;
}
bool Que(int p, int L, int R, int st, int ed) {
if(!p || st > ed) return false;
if(L == st && ed == R) return true;
int m = L + R >> 1; bool r = false;
if(st <= m) r |= Que(ls[p], L, m, st, std::min(ed, m));
if(ed > m) r |= Que(rs[p], m + 1, R, std::max(m + 1, st), ed);
return r;
}
}seg;
struct SAM {
int ch[N][26], mx[N], fa[N], last, top;
void Init() {re(ch[1]); last = top = 1