zoukankan      html  css  js  c++  java
  • 【可持久化线段树】【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
    " : "Yes
    ") ;
        }
        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

  • 相关阅读:
    (转)I/O Completion Ports学习
    mysql 二进制字段拆分转换
    记录一些硬件开源项目网址
    线圈式电磁炮1----原理介绍
    线圈式电磁炮2----硬件系统搭建
    电机控制7---步进电机模型及控制(4)
    电机控制6---步进电机模型及控制(3)
    电机控制8---步进电机常见问题
    电机控制5---步进电机模型及控制(2)
    电机控制4---步进电机模型及控制(1)
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/12052242.html
Copyright © 2011-2022 走看看