子序列自動機
阿新 • • 發佈:2022-03-29
子序列自動機
這東西是我刷 ARC 的時候遇到的,慕名而來。
結合模板題閱讀:
https://www.luogu.com.cn/problem/P5826
構建
這個自動機原理十分簡單,你可以將它當作一個 dp 來食用:
記所給的字串為 \(w\),字符集為 \(S\),\(next[i][ch]\) 為第 \(i\) 個字元之後(不包括位置 \(i\))字元 \(ch\) 所在的最近的位置。
那麼我們有轉移方程:
\[next[i][j] = next[i+1][j] ~~~~~~~ (w[i+1] \ne j) \\ next[i][j] = i+1 ~~~~~~~ (w[i+1] = j) \]注意到轉移的方向,我們只需要倒序掃一遍字串並構建 \(next\)
優化
當字符集 \(S\) 大小 \(|S|\) 很小的時候,直接轉移就夠了。
那,\(|S|\) 比較大的時候如何處理呢?
注意到對於上面的轉移方程,其實只有字元 \(j = w[i+1]\) 的時候 \(next[i][j]\) 才會被更新,那麼我們不妨將 \(next[i]\) 看成是一個桶(值域是字符集),則我們需要做的操作就是在桶的位置 \(j\) 作單點修改。
而因為我們需要開 \(n\)(字串 \(w\) 長度)次桶,每次都相應地作單點修改,因此這一過程可以用主席樹來維護。
查詢是否存在所求子序列
由 \(next\) 構建過程知這是基於貪心的思想構建的,所以我們將待查詢的串 \(str\)
也就是本模板題,程式碼見下面的實現。
實現
截至目前,洛谷最優解有五頁,我排到第四頁,QAQ。
#include<bits/stdc++.h> using namespace std; #define debug(x) cerr << #x << ": " << (x) << endl #define rep(i,a,b) for(int i=(a);i<=(b);i++) #define dwn(i,a,b) for(int i=(a);i>=(b);i--) #define pb push_back #define all(x) (x).begin(), (x).end() #define x first #define y second using pii = pair<int, int>; using ll = long long; inline void read(int &x){ int s=0; x=1; char ch=getchar(); while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();} while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar(); x*=s; } const int N=1e5+5; struct Node{ int l, r; int go; }tr[N*25]; int root[N], idx; int qwq, n, q, m; int w[N]; void upd(int &p, int q, int l, int r, int x, int k){ p=++idx; tr[p]=tr[q]; if(l==r){; tr[p].go=k; return; } int mid=l+r>>1; if(x<=mid) upd(tr[p].l, tr[q].l, l, mid, x, k); else upd(tr[p].r, tr[q].r, mid+1, r, x, k); } void build(){ dwn(i,n,1) upd(root[i-1], root[i], 1, m, w[i], i); } int query(int u, int l, int r, int x){ if(l==r) return tr[u].go; int mid=l+r>>1; if(x<=mid) return query(tr[u].l, l, mid, x); return query(tr[u].r, mid+1, r, x); } int main(){ cin>>qwq>>n>>q>>m; rep(i,1,n) read(w[i]); build(); while(q--){ int k; read(k); bool ok=true; int u=0; while(k--){ int x; read(x); auto go=query(root[u], 1, m, x); if(!go) ok=false; if(ok) u=go; } puts(ok? "Yes": "No"); } return 0; }