【可持久化線段樹】【P5826】【模板】子序列自動機
Description
給定一個序列 \(A\),有 \(q\) 次詢問,每次詢問一個序列 \(B\) 是不是 \(A\) 的子序列
Limitations
序列 \(A\) 長度不超過 \(10^5\),詢問序列長度之和不超過 \(10^6\),詢問次數不超過 \(10^5\)
Solution
題外話:有關這道題的難度,我覺得大概到不了紫色,但是可持久化線段樹的板子是紫色的,所以就設成了紫色
Algorithm \(1\)
考慮對於一個詢問序列 \(B\),設其與 \(A\) 的最長公共子序列在 \(A\) 中的下標序列爲 \(Z\),顯然當且僅當 \(Z\) 的長度爲 \(|B|\) 時,\(B\) 是 \(A\) 的子序列。合法的序列 \(Z\) 可能會有多個,但是隻要我們找到了字典序最小的長度爲 \(|B|\) 的序列 \(Z\),就可以說明 \(B\) 是 \(A\) 的子序列,否則不是。
考慮尋找字典序最小的 \(Z\) 可以貪心的選擇,即對於 \(B\) 的每個前綴,可以求出其對應的 \(Z\) 序列的最後一位最小是多少,當 \(B\) 的前綴新增一個數字時,只需要在 \(A\) 中從當前 \(Z\) 序列最後一位的值的位置開始繼續向後掃描,掃到第一個等於新增數字的位置,即是新的 \(Z\) 序列的最後一位。而如果掃描到了 \(A\) 的最後也沒有找到,則意味着不存在合法的 \(Z\) 序列,因此 \(B\) 不是 \(A\) 的子序列。
這樣的話每次詢問時,最多掃描 \(A\) 一次,因此總時間複雜度爲 \(O(nq + \sum L)\),可以通過 Subtask \(1\),期望得分 \(20~pts\)
Algorithm \(2\)
考慮對 \(A\) 建立一個子序列自動機,用來識別 \(A\) 的所有子序列。
同樣運用 Algorithm 1
中的思想,對於一個字符串\(B\),我們只要找到了其與 \(A\) 的最長公共子序列在 \(A\) 中的字典序最小的下標序列 \(Z\),就可以說明 \(B\) 是 \(A\) 的子序列。那麼對於 \(A\) 的每一位而言,在其需要新匹配一個數字時,應該轉移到 \(A\) 後面第一個爲該數字的位置,顯然這樣才能保證 \(Z\) 序列的字典序是最小的。因此我們的轉移應該對每個位置維護加入一個數字以後它後面第一個爲該數字的位置。
考慮我們對 \(A\) 從後向前逐位建立自動機,對於第 \(i\) 位而言,第 \(i - 1\) 位加入 \(A_i\) 應該轉移到 \(i\),而加入其它數字應該轉移到 \(A_i\) 加入該數字後轉移到的位置。因此有僞代碼
for i : m do
trans[n][i] <- -1
end
for i = n : 1 do
for j = 1 : m do
trans[i - 1][j] <- trans[i][j]
end
trans[i - 1][A[i]] <- i
end
其中 \(n\) 代表 \(A\) 的長度,\(m\) 代表 \(A\) 中的最大值,\(trans\) 是一個二維數組,代表這個自動機。
而對一個字符串 \(B\) 進行匹配時,只需要將 \(B\) 順着自動機的轉移跑一遍,若沒有跑出自動機,則 \(B\) 是 \(A\) 的子序列,否則不是。
Function check:
pos <- 0
ret <- true
for i = 1 : L do
pos <- trans[pos][B[i]]
if pos == -1 then
ret <- false
break
endif
end
return ret
end Func
注意到這樣構造自動機的時間複雜度爲 \(O(nm)\),匹配的複雜度爲 \(O(\sum L)\),因此總時間複雜度 \(O(nm + \sum L)\),可以通過 Subtask \(1\),\(2\),期望得分 \(55~pts\)。
Algorithm \(3\)
注意到構造自動機時,第 \(i\) 位與第 \(i - 1\) 位只有 \(A_i\) 一項不一樣,第 \(i - 1\) 位的轉移可以看做第 \(i\) 位的轉移的基礎上修改了一個位置,因此我們可以從後向前使用可持久化線段樹來維護每個位置的轉移數組,這樣建立自動機的時間複雜度爲 \(O(n \log m)\),匹配的時間複雜度爲 \(O(\sum L \log m)\)。總時間複雜度 \(O((n + \sum L) \log m)\),可以通過全部的 Subtask,期望得分 \(100~pts\)。
Code
Algorithm \(2\)
代碼來自 @**_皎月半灑花**
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define MAXN 200010
using namespace std ;
int L, N, M, Q, S[MAXN], nxt[MAXN][102] ;
void build(){
for (int i = 1 ; i <= M ; ++ i)
nxt[L + 2][i] = nxt[L + 1][i] = L + 2 ;
for (int i = L ; i ; -- i)
memcpy(nxt[i - 1], nxt[i], sizeof(nxt[i])), nxt[i - 1][S[i]] = i ;
}
int qr(){
char c = getchar() ;
int res = 0 ; while (!isdigit(c)) c = getchar() ;
while (isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar() ;
return res ;
}
int main(){
int i, j, k, emm ;
cin >> emm >> N >> Q >> M ; L = N ;
for (i = 1 ; i <= L ; ++ i) scanf("%d", &S[i]) ; build() ;
for (i = 1 ; i <= Q ; ++ i){
N = qr() ; int st = 0, ans = 0 ;
for (j = 1 ; j <= N ; ++ j){
k = qr(), st = nxt[st][k] ;
if (!st){
while (j < N)
++ j, emm = qr() ;
ans = 1 ;
}
// cout << st << endl ;
}
printf(ans ? "No\n" : "Yes\n") ;
}
return 0 ;
}
Algorithm \(3\)
#include <cstdio>
template <typename T>
inline void qr(T &x) {
char ch;
do ch = getchar(); while ((ch > '9') || (ch < '0'));
do x = x * 10 + (ch ^ 48), ch = getchar(); while ((ch >= '0') && (ch <= '9'));
}
const int maxn = 100005;
struct Tree {
Tree *ls, *rs;
int l, r, v;
Tree(const int L, const int R) : l(L), r(R), v(-1) {
if (l != r) {
int mid = (l + r) >> 1;
ls = new Tree(l, mid);
rs = new Tree(mid + 1, r);
}
}
Tree(Tree *pre, const int P, const int V) : l(pre->l), r(pre->r), v(0) {
if (l == r) {
v = V;
} else {
if (pre->ls->r >= P) {
rs = pre->rs;
ls = new Tree(pre->ls, P, V);
} else {
ls = pre->ls;
rs = new Tree(pre->rs, P, V);
}
}
}
int query(const int x) {
if (this->l == this->r) {
return this->v;
} else {
return (this->ls->r >= x) ? this->ls->query(x) : this->rs->query(x);
}
}
};
Tree *rot[maxn];
int tp, n, q, m;
int MU[maxn];
int main() {
qr(tp); qr(n); qr(q); qr(m);
rot[n] = new Tree(1, m);
for (int i = 1; i <= n; ++i) {
qr(MU[i]);
}
for (int i = n; i; --i) {
rot[i - 1] = new Tree(rot[i], MU[i], i);
}
for (int L, x, pos; q; --q) {
L = pos = 0; qr(L);
while ((L--) && (pos != -1)) {
x = 0; qr(x);
if ((pos = rot[pos]->query(x)) == -1) {
while (L--) {
qr(x);
}
break;
}
}
puts((~pos) ? "Yes" : "No");
}
return 0;
}
appreciation
感謝驗題人:@**_皎月半灑花** @water_lift
感謝本文的審覈與校對:@Dusker