1. 程式人生 > 其它 >AC自動機題目選講

AC自動機題目選講

AC自動機題目選講

AC自動機題目選講

AC自動機複習:AC 自動機 - OI Wiki

先完成模板:luogu的

下面的所有例題程式碼我用的是ldytxdy這個賬號提交,可以直接在cf上檢視程式碼。

複習題

\(1.\)CF1202E

列舉斷點,設 \(\large s_i\)​​ 和 \(\large rs_i\) ​​分別表示以\(i\)​​為結尾的字首/字尾的匹配個數,答案為

\[\large \sum_{i=2}^{n}s_i \times rs_{i-1} \]

\(\large s_i\) ​和 \(\large rs_i\) ​可以通過分別建正反串AC自動機求出。

AC自動機上dp

\(2\)​.CF696D

\(\large f_{i,j}\)​​ 為答案串匹配到前\(i\)​​位,在AC自動機的 \(\large j\) ​​節點的最大值

\(\large val(x)\) ​表示把原串權值記在\(x\)​號點

\[\large f_{0,0} = 1\\ \large f_{i,j} + val(tr_{j,k})\to f_{i+1,tr_{j,k}} \]

矩乘優化即可。

\(3\)​.SDOI2014 數數

\(\large \ f_{pos,p}\)​​​ 表示答案數位在 \(\large pos\)​​​ 且AC自動機節點在 \(\large p\)​​​ 的方案數

\(\large op(x)\)

​​ 為 \(x\)​​ 是否為結束節點

\[\large f_{0,0}=1\\ \large f_{i,j} \to f_{i+1,tr_{j,k}}(op(tr_{j,k})=false) \]

用數位dp實現這個過程

練一練:CF86C

簡單資料結構+AC自動機

\(5.\)​​​​NOI2011 阿狸的打字機

考慮題目中加減字元的實際意義就是在trie樹上dfs,所以我們把詢問離線到每個結束點上做一遍dfs

我們建出fail樹,考慮AC自動機跳fail樹的匹配過程,需要實現的操作就是單點加查子樹和

記下fail樹的dfn序後樹狀陣列,記得dfs回溯的時候撤銷貢獻。

\(6\)​​​​.CF547E

考慮離線,每個詢問可以拆成字首和相減的形式,用vector記錄每個位置的詢問

每掃到一個字串,在fail樹上把這個字串代表的所有節點權值 \(\large +1\)

答案就是查 \(\large s_k\)​ 在fail樹上結束點的子樹和。套用上題做法即可。

\(7.\)​​CF1437G

根據上兩題的經驗很容易做出來,需要實現單點加和點到根的總權值,樹剖維護。

練一練:CF163E

好題

\(8\)​.CF710F

CF163E的線上版本

AC自動機的刪除是很困難的

建出兩個AC自動機,分別塞入刪除和增加的串,答案就是在兩個AC自動機上分別做一遍匹配然後相減。

考慮二進位制分組,每組建一個AC自動機,加入時往前合併,具體實現與線段樹合併類似。

顯然每個串至多合併 \(\large log_2n\)​​ 次,所以複雜度是 \(\large Slogn\)​ ​, \(\large S\)​​ 表示總串長。

\(9.\)​​​CF587F

因為我們做了CF547E,所以可以很容易看出答案就是:

\(\large l...r\)​​ 中所有\(\large s_i\)​​的結束點子樹每個點 \(\large +1\)​ ​後 \(\large s_k\)​​ 代表字元所有節點的總權值。

這樣就不能像之前的題用樹狀陣列的單點加實現了,考慮離線根號分治。

\(\large len_i\)​​ ​​​表示 \(\large s_i\)​​​​​ 的長度,設所有 \(\large s_i\)​​​​​ 總長度為 \(\large M\)​​ ​​​,並且設一個閾值 \(\large L\) ​​​​​.

\(\large I.\)\(\large len_i \leqslant L\)​​

依次掃過 \(n\)​ 個串,每掃到一個串用樹狀陣列實現子樹加,查的時候暴力單點查一個字串的所有點。

複雜度分析:子樹加是 $\large O(nlog_2M) $​​​的,查詢是 \(\large O(QLlog_2M)\)​ ​​的

\(\large II.\)\(\large len_i>L\)

顯然這樣的 \(\large s_i\)​​ 個數是不超過 \(\large \frac M L\) ​​的

還是依次掃過 \(\large n\)​​ 個串,我們對於每個 \(\large s_i\)​​ 的節點做一次單點加,查就是一個子樹和。

這個可以不用資料結構,暴力做就好了。

複雜度分析:單點加: \(\large O(\frac {M^2} L)\)​​​​ ,因為要把詢問排序所以詢問的複雜度是 \(\large O(Qlog_2Q)\) ​​​​的

利用均值不等式設 \(\large \frac {M^2} L = QLlog_2n\)​​ 解得 \(\large L=\frac {m} {\sqrt {qlog_2m}}\)​​

所以總複雜度是 \(\large O(nlog_2M+Qlog_2Q+M\sqrt {Qlog_2M})\)

\(10.\)​​​CF1483F

顯然 \(\large (i,j)\)​​​​​合法當且 \(\large s_j\)​​ ​​​在 \(\large s_i\)​​​​​ 中不被任意一個 \(\large s_k\) ​​​​​覆蓋

我們列舉每個字串作為 \(\large i\)​​​,從後往前列舉 \(\large s_i\)​​​的每個位置 \(\large p\)​​​

我們需要尋找出以 \(\large p\)​​​ ​​結尾的最長子串 \(\large S\)​​​​​ 使得 \(\large S \in s_{1...n}\)​​​​​,設 \(\large S=s_x\)​​​​​

\(\large S\)​​​​​ 的左端點為 \(\large L\)​​​​​,設 \(\large pre\)​ ​​​​為之前掃過的最大的 \(\large L\)​​​​​

如果 \(\large pre>L\)​​​ ​那麼這就是 \(\large s_x\)​​ ​​不被包含的一次,此時 \(\large cnt_{s_x}+1\)​​​​,並用 \(\large L\)​ ​​​更新 \(\large pre\)​​​​

最後答案加上有多少個 \(\large cnt_i\)​ ​滿足 \(\large cnt_i=\)​​ 總出現個數

經典工業題

luoguP5599 XR-4 文字編輯器

模板

最後附上我的板子,是這個的AC程式碼。

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n;char s[N];
int tr[N][27],fail[N],End[N],tot;
queue<int>q;
void ins(char *s){
	int p=0,m=strlen(s);
	for(int i=0;i<m;i++){
		int l=s[i]-'a';
		if(!tr[p][l])
			tr[p][l]=++tot;
		p=tr[p][l];
	}
	End[p]++;
}
void build(){
	for(int i=0;i<26;i++)
		if(tr[0][i])q.push(tr[0][i]);
	while(q.size()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++){
			if(tr[u][i]){
				fail[tr[u][i]]=tr[fail[u]][i];
				q.push(tr[u][i]);
			}
			else tr[u][i]=tr[fail[u]][i];
		}
	}
}
int query(char *s){
	int m=strlen(s),p=0,ans=0;
	for(int i=0;i<m;i++){
		p=tr[p][s[i]-'a'];
		for(int t=p;t&&~End[p];t=fail[t])
			ans+=End[t],End[t]=-1;
	}return ans;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",s);
		ins(s);
	}
	build();
	scanf("%s",s);
	return printf("%d\n",query(s))&0;
}