【可持久化線段樹】【P5826】【模板】子序列自動機


【可持久化線段樹】【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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM