待办:
学习回文树。
AC 自动机与回文树例题若干。
题单:
P5357 【模板】AC 自动机
模板,上代码:
C++
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
struct ACAM{
int tr[N][30];
int fail[N],cnt[N],in[N],id[N],ask[N],ans[N];
int midx,didx;
void init(){
midx=didx=0;
clear(0);
}
void clear(int p){
memset(tr[p],0,sizeof tr[p]);
fail[p]=cnt[p]=in[p]=id[p]=0;
}
void insert(string s,int qid){
int p=0;
for(int i=0;i<s.size();i++){
if(!tr[p][s[i]-'a']){
tr[p][s[i]-'a']=++didx;
clear(tr[p][s[i]-'a']);
}
p=tr[p][s[i]-'a'];
}
if(!id[p]) id[p]=++midx;
ask[qid]=id[p];
}
void build(){
queue<int> q;
for(int i=0;i<26;i++) if(tr[0][i]) q.push(tr[0][i]);
while(q.size()){
int p=q.front();
q.pop();
for(int i=0;i<26;i++){
if(tr[p][i]){
fail[tr[p][i]]=tr[fail[p]][i],in[tr[fail[p]][i]]++;
q.push(tr[p][i]);
}
else tr[p][i]=tr[fail[p]][i];
}
}
}
void query(string s){
int p=0;
for(int i=0;i<s.size();i++){
p=tr[p][s[i]-'a'];
cnt[p]++;
}
}
void topu(){
queue<int> q;
for(int i=1;i<=didx;i++) if(!in[i]) q.push(i);
while(q.size()){
int p=q.front();
q.pop();
ans[id[p]]=cnt[p],cnt[fail[p]]+=cnt[p],in[fail[p]]--;
if(!in[fail[p]]) q.push(fail[p]);
}
}
int get(int qid){
return ans[ask[qid]];
}
}acam;
int main(){
int n;
string t,s;
acam.init();
cin>>n;
for(int i=1;i<=n;i++){
cin>>t;
acam.insert(t,i);
}
acam.build();
cin>>s;
acam.query(s);
acam.topu();
for(int i=1;i<=n;i++) cout<<acam.get(i)<<"\n";
return 0;
}P2444 [POI 2000 R1] 病毒
首先,我们需要知道一个事情,那就是 AC 自动机构成的 Trie 图是一个完备的“确定性有限状态自动机”,用人话讲就是所有字符集内的有限串都是能在自动机里找到的。
题目里说的无限 01 串实际上可以看作是有限 01 串无限循环下去,映射到 Trie 图上就是一个环,所以我们构建 Trie 图后找环即可。
对于病毒串,我们在串尾打上病毒标记,找环时不走到这个状态即可。
代码:
C++
#include<bits/stdc++.h>
using namespace std;
const int N=3e4+5;
struct ACAM{
int tr[N][2];
int fail[N],tag[N],dan[N];
int didx;
void init(){
didx=0;
clear(0);
}
void clear(int p){
memset(tr[p],0,sizeof tr[p]);
fail[p]=0;
}
void insert(string s){
int p=0;
for(int i=0;i<s.size();i++){
if(!tr[p][s[i]-'0']){
tr[p][s[i]-'0']=++didx;
clear(tr[p][s[i]-'0']);
}
p=tr[p][s[i]-'0'];
}
dan[p]=1;
}
void build(){
queue<int> q;
for(int i=0;i<2;i++) if(tr[0][i]) q.push(tr[0][i]);
while(q.size()){
int p=q.front();
q.pop();
for(int i=0;i<2;i++){
if(tr[p][i]){
fail[tr[p][i]]=tr[fail[p]][i];
q.push(tr[p][i]);
if(dan[tr[fail[p]][i]]) dan[tr[p][i]]=1;
}
else tr[p][i]=tr[fail[p]][i];
}
}
}
bool find(int u){
if(tag[u]==1) return 1;
if(tag[u]==-1) return 0;
tag[u]=1;
for(int i=0;i<2;i++) if(!dan[tr[u][i]]) if(find(tr[u][i])) return 1;
tag[u]=-1;
return 0;
}
}acam;
int main(){
int n;
string t;
acam.init();
cin>>n;
for(int i=1;i<=n;i++){
cin>>t;
acam.insert(t);
}
acam.build();
if(acam.find(0)) cout<<"TAK";
else cout<<"NIE";
return 0;
}P3041 [USACO12JAN] Video Game G
先对所有“组合技”串插入到自动机里,考虑一个状态的价值应该是其 fail 祖先链上的节点价值之和,我们在 build 的时候记录这样一个 $vis$ 数组,之后进行 DP,设 $dp_{i,j}$ 表示选了 $i$ 个字符,当前在 Trie 上的节点 $j$,有转移方程:
$$
dp_{i,tr[j][k]}=\max _{k\in \{'A','B','C'\}}(dp_{i,tr[j][k]},dp_{i-1,j}+vis_{tr[j][k]})
$$
代码:
C++
#include<bits/stdc++.h>
using namespace std;
const int N=305,M=1e3+5;
struct ACAM{
int tr[N][3];
int fail[N],vis[N],dp[M][N];
int didx;
void init(){
didx=0;
clear(0);
}
void clear(int p){
memset(tr[p],0,sizeof tr[p]);
fail[p]=vis[p]=0;
}
void insert(string s){
int p=0;
for(int i=0;i<s.size();i++){
if(!tr[p][s[i]-'A']){
tr[p][s[i]-'A']=++didx;
clear(tr[p][s[i]-'A']);
}
p=tr[p][s[i]-'A'];
}
vis[p]++;
}
void build(){
queue<int> q;
for(int i=0;i<3;i++) if(tr[0][i]) q.push(tr[0][i]);
while(q.size()){
int p=q.front();
q.pop();
for(int i=0;i<3;i++){
if(tr[p][i]){
fail[tr[p][i]]=tr[fail[p]][i];
vis[tr[p][i]]+=vis[tr[fail[p]][i]];
q.push(tr[p][i]);
}
else tr[p][i]=tr[fail[p]][i];
}
}
}
int getans(int k){
int res=0;
memset(dp,-0x3f,sizeof dp);
dp[0][0]=0;
for(int i=1;i<=k;i++) for(int j=0;j<=didx;j++) for(int l=0;l<3;l++){
if(dp[i-1][j]<0) continue;
dp[i][tr[j][l]]=max(dp[i][tr[j][l]],dp[i-1][j]+vis[tr[j][l]]);
}
for(int i=0;i<=didx;i++) res=max(res,dp[k][i]);
return res;
}
}acam;
int main(){
int n,k;
string t;
acam.init();
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>t;
acam.insert(t);
}
acam.build();
cout<<acam.getans(k);
return 0;
}


Comments NOTHING