kuangbin專題十七AC自動機總結
這個專題寫的我頭皮發麻,出現了好多小bug耗費了我好多時間,但總體看不算太難,只要把思路縷清就行了。
AC自動機的題目有兩類,一類是字串找子串個數的,另一類則是建立狀態,然後進行dp或者矩陣快速冪。
B - 病毒侵襲
這題不算太難,但有一個坑點,就是字元都是ASCII碼可見字元。
算上空格, 從32到126共95個可見字元.
不算上空格則為94個.
所以我們讀入需要用gets,而字典樹得開到126以上。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct Trie
{
int next[maxn][128],end[maxn],fail[maxn];
int root,L;
int newnode()
{
for(int i=0; i<128; i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init()
{
L=0;
root=newnode();
}
void insert(char buf[],int idx)
{
int len=strlen(buf);
int now=root;
for(int i=0; i<len; i++)
{
if(next[now][buf[i]]==-1)
next[now][buf[i]]=newnode();
now=next[now][buf[i]];
}
end[now]=idx;
}
void build()
{
int now=root;
fail[root]=root;
queue <int>Q;
for(int i=0; i<128; i++)
{
if(next[now][i]==-1)
next[now][i]=root;
else
{
fail[next[now][i]]=root;
Q.push(next[now][i]);
}
}
while(!Q.empty())
{
now=Q.front();
Q.pop();
for(int i=0; i<128; i++)
{
if(next[now][i]==-1)
next[now][i]=next[fail[now]][i];
else
{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
}
void Query(char buf[],set<int>&S)
{
int len=strlen(buf);
int now=root;
for(int i=0; i<len; i++)
{
now=next[now][buf[i]];
int temp=now;
while(temp!=root)
{
if(end[temp]!=0)
S.insert(end[temp]);
temp=fail[temp];
}
}
}
void debug()
{
for(int i=0; i<L; i++)
{
printf("id = %3d,fail = %3d,end = %3d,chi = [",i,fail[i],end[i]);
for(int j=0; j<128; j++)
printf("%2d",next[i][j]);
printf("]\n");
}
}
};
Trie ac;
char s[10005];
set<int>S;
int main()
{
int N;
while(~scanf("%d",&N))
{
ac.init();
getchar();
for(int i=1; i<=N; i++)
{
gets(s);
ac.insert(s,i);
}
ac.build();
//ac.debug();
int q;
scanf("%d",&q);
getchar();
int ans=0;
set<int>::iterator itor;
for(int i=1; i<=q; i++)
{
gets(s);
S.clear();
ac.Query(s,S);
if(S.size())
{
ans++;
printf("web %d:",i);
for(itor=S.begin(); itor!=S.end(); itor++)
printf(" %d",*itor);
puts("");
}
}
printf("total: %d\n",ans);
}
}
D - Detect the Virus
這題唯一的麻煩的一點就是進位制之間的轉換,轉換之後就是普通的ac自動機。
這題錯了好幾次,提示Segmentation Fault,一開始以為陣列開小了,結果沒想到是陣列開得太大了。。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
#include<vector>
#include<set>
using namespace std;
const int maxn=53769;
const int maxm=15010;
struct Trie
{
int Next[maxn][260],fail[maxn],end[maxn];
int f[maxn];
int root,L;
int newnode()
{
for(int i=0;i<256;i++)
Next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init()
{
L=0;
root=newnode();
}
void insert(int num[],int len)
{
int now=root;
for(int i=0;i<len;i++)
{
if(Next[now][num[i]]==-1)
Next[now][num[i]]=newnode();
now=Next[now][num[i]];
}
end[now]++;
}
void build()
{
int now=root;
fail[root]=root;
queue<int>Q;
for(int i=0;i<256;i++)
{
if(Next[now][i]==-1)
Next[now][i]=root;
else
{
fail[Next[now][i]]=root;
Q.push(Next[now][i]);
}
}
while(!Q.empty())
{
now=Q.front();Q.pop();
for(int i=0;i<256;i++)
{
if(Next[now][i]==-1)
Next[now][i]=Next[fail[now]][i];
else
{
fail[Next[now][i]]=Next[fail[now]][i];
Q.push(Next[now][i]);
}
}
}
}
int Query(int num[],int len)
{
memset(f,0,sizeof(f));
int res=0;
int now=root;
for(int i=0;i<len;i++)
{
now=Next[now][num[i]];
int temp=now;
while(temp!=root)
{
if(end[temp]!=0&&!f[temp])
res+=end[temp],f[temp]=1;
temp=fail[temp];
}
}
return res;
}
};
Trie ac;
char s[maxm];
int getid(char s)
{
if(s>='A'&&s<='Z')return s-'A';
if(s>='a'&&s<='z')return s-'a'+26;
if(s>='0'&&s<='9')return s-'0'+52;
if(s=='+')return 62;
return 63;
}
vector<int>V;
int ans[maxn];
int solve(char s[],int flag)
{
V.clear();
int len=strlen(s);
int tmp[10];
for(int i=0;i<len;i++)
{
if(s[i]=='=')break;
int num=getid(s[i]);
for(int j=0;j<6;j++)
{
tmp[j]=num%2;
num/=2;
}
for(int j=5;j>=0;j--)
V.push_back(tmp[j]);
}
int Size=V.size();
int num=Size/8;
int cnt=0;
for(int i=0;i<num;i++)
{
int thenum=0;
for(int j=8*i;j<8*i+8;j++)
thenum=2*thenum+V[j];
ans[cnt++]=thenum;
}
if(!flag)
{
ac.insert(ans,cnt);
return 0;
}
else
{
return ac.Query(ans,cnt);
}
}
int main()
{
int N;
while(~scanf("%d",&N))
{
ac.init();
for(int i=1;i<=N;i++)
{
scanf("%s",s);
solve(s,0);
}
//ac.Debug();
ac.build();
int q;
scanf("%d",&q);
while(q--)
{
scanf("%s",s);
printf("%d\n",solve(s,1));
}
puts("");
}
return 0;
}
E - DNA Sequence
對於這題我想說明什麼時候會建立矩陣,什麼時候會dp。我們可以發現這兩種方法實際上都是狀態的轉移,而建立矩陣的時候往往步數會很多,例如這題n達到了2e9,所以不得不用矩陣,然後進行矩陣快速冪。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn=105;
const int mod=100000;
typedef long long ll;
int tn;
struct Mat
{
ll mat[maxn][maxn];
Mat()
{
memset(mat,0,sizeof(mat));
}
void out()
{
for(int i=0; i<tn; i++)
{
for(int j=0; j<tn; j++)
cout<<mat[i][j]<<" ";
cout<<endl;
}
}
};
Mat operator*(const Mat &m1,const Mat &m2)
{
Mat m;
for(int i=0; i<tn; i++)
for(int j=0; j<tn; j++)
for(int k=0; k<tn; k++)
{
m.mat[i][j]+=(m1.mat[i][k]*m2.mat[k][j])%mod;
m.mat[i][j]%=mod;
}
return m;
}
Mat quick_mul(int num,Mat a)
{
Mat e;
for(int i=0; i<tn; i++)
e.mat[i][i]=1;
while(num)
{
if(num%2)
e=e*a;
num/=2;
a=a*a;
}
return e;
}
int getid(char s)
{
if(s=='A')return 0;
if(s=='T')return 1;
if(s=='C')return 2;
return 3;
}
struct Trie
{
int Next[maxn][4],fail[maxn],end[maxn];
int flag[maxn];
int root,L;
int newnode()
{
for(int i=0; i<4; i++)
Next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init()
{
memset(flag,0,sizeof(flag));
L=0;
root=newnode();
}
void insert(char buf[])
{
int len=strlen(buf);
int now=root;
for(int i=0; i<len; i++)
{
int id=getid(buf[i]);
if(Next[now][id]==-1)
Next[now][id]=newnode();
now=Next[now][id];
}
end[now]++;
flag[now]=1;
}
void build()
{
int now=root;
fail[root]=root;
queue<int>Q;
for(int i=0; i<4; i++)
{
if(Next[now][i]==-1)
Next[now][i]=root;
else
{
fail[Next[now][i]]=root;
Q.push(Next[now][i]);
}
//flag[Next[now][i]]|=flag[Next[fail[now]][i]];
}
while(!Q.empty())
{
now=Q.front();
Q.pop();
for(int i=0; i<4; i++)
{
if(Next[now][i]==-1)
Next[now][i]=Next[fail[now]][i];
else
{
fail[Next[now][i]]=Next[fail[now]][i];
Q.push(Next[now][i]);
}
flag[Next[now][i]]|=flag[Next[fail[now]][i]];
}
}
}
void buildmatrix(Mat &a)
{
for(int i=0; i<L; i++)if(!flag[i])
for(int j=0; j<4; j++)if(!flag[Next[i][j]])
a.mat[i][Next[i][j]]++,a.mat[i][Next[i][j]]%=mod;
}
};
char s[15];
Trie ac;
int main()
{
int m,n;
while(~scanf("%d %d",&m,&n))
{
ac.init();
for(int i=1; i<=m; i++)
{
scanf("%s",s);
ac.insert(s);
}
Mat a;
ac.build();
tn=ac.L;
ac.buildmatrix(a);
//a.out();
Mat ans;
ans=quick_mul(n,a);
ll theans=0;
for(int i=0; i<tn; i++)
theans=(theans+ans.mat[0][i])%mod;
printf("%lld\n",theans);
}
return 0;
}
H - Wireless Password
這題被卡常了,怪我自己程式碼寫醜了。
兩個數的按位異或等於兩個數直接異或!
#include<bits/stdc++.h>
using namespace std;
const int mod=20090717;
const int maxn=105;
typedef long long ll;
struct Trie
{
int Next[maxn][26],fail[maxn],end[maxn];
int root,L;
int newnode()
{
for(int i=0;i<26;i++)
Next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init()
{
L=0;
root=newnode();
}
void insert(char s[],int idx)
{
int len=strlen(s);
int now=root;
for(int i=0;i<len;i++)
{
int go=s[i]-'a';
if(Next[now][go]==-1)
Next[now][go]=newnode();
now=Next[now][go];
}
end[now]=(1<<idx);
}
void build()
{
int now=root;
queue<int>Q;
for(int i=0;i<26;i++)
{
if(Next[now][i]==-1)
Next[now][i]=root;
else
{
fail[Next[now][i]]=root;
Q.push(Next[now][i]);
}
}
while(!Q.empty())
{
now=Q.front();Q.pop();
if(end[fail[now]])end[now]+=end[fail[now]];
for(int i=0;i<26;i++)
{
if(Next[now][i]==-1)
Next[now][i]=Next[fail[now]][i];
else
{
fail[Next[now][i]]=Next[fail[now]][i];
Q.push(Next[now][i]);
}
}
}
}
};
Trie ac;
int dp[30][maxn][1025];
int n,m,k;
bool check(int num)
{
int res=0;
while(num)
{
res+=num%2;
num/=2;
}
if(res>=k)return 1;
return 0;
}
int main()
{
while(~scanf("%d %d %d",&n,&m,&k)&&n)
{
ac.init();
char s[15];
for(int i=0;i<m;i++)
{
scanf("%s",s);
ac.insert(s,i);
}
memset(dp,0,sizeof(dp));
ac.build();
//Matrix to=ac.buildmatrix();
//to.out();
dp[0][0][0]=1;
int statenum=ac.L;
for(int i=1;i<=n;i++)
for(int j=0;j<statenum;j++)
for(int kase=0;kase<(1<<m);kase++)if(dp[i-1][j][kase])
for(int t=0;t<26;t++)
{
int v=ac.Next[j][t];
if(ac.end[v]!=-1)
{
int it=ac.end[v];
int tmp=kase|it;
dp[i][v][tmp]=(dp[i][v][tmp]+dp[i-1][j][kase])%mod;
}
else
{
dp[i][v][kase]=(dp[i][v][kase]+dp[i-1][j][kase])%mod;
}
}
int ans=0;
for(int i=0;i<(1<<m);i++)
{
if(check(i))
{
for(int j=0;j<statenum;j++)
ans=(ans+dp[n][j][i])%mod;
}
}
cout<<ans<<endl;
}
return 0;
}
I - Ring
這題還是wa了好幾次,原因是不知道怎麼求字典序最小的串,最後看別人的才知道存起來就完事了。。
另外當找不到時,應該輸出空字元而不應該輸出’a’。。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1110;
typedef long long ll;
struct Trie
{
int Next[maxn][26],fail[maxn],end[maxn];
int root,L;
int newnode()
{
for(int i=0; i<26; i++)
Next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init()
{
L=0;
root=newnode();
}
void insert(char s[],int cost)
{
int now=root;
int len=strlen(s);
for(int i=0; i<len; i++)
{
int go=s[i]-'a';
if(Next[now][go]==-1)
Next[now][go]=newnode();
now=Next[now][go];
}
end[now]+=cost;
}
void build()
{
int now=root;
queue<int>Q;
for(int i=0; i<26; i++)
{
if(Next[now][i]==-1)
Next[now][i]=root;
else
{
fail[Next[now][i]]=root;
Q.push(Next[now][i]);
}
}
while(!Q.empty())
{
now=Q.front();
Q.pop();
if(end[fail[now]])
end[now]+=end[fail[now]];
for(int i=0; i<26; i++)
{
if(Next[now][i]==-1)
Next[now][i]=Next[fail[now]][i];
else
{
fail[Next[now][i]]=Next[fail[now]][i];
Q.push(Next[now][i]);
}
}
}
}
};
Trie ac;
char s[110][15];
int cost[110];
ll dp[55][maxn];
string rout[55][maxn];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
for(int i=0;i<55;i++)
for(int j=0;j<maxn;j++)rout[i][j]="";
int n,m;
ac.init();
scanf("%d %d",&n,&m);
for(int i=1; i<=m; i++)
scanf("%s",s[i]);
for(int i=1; i<=m; i++)
scanf("%d",&cost[i]);
for(int i=1; i<=m; i++)
ac.insert(s[i],cost[i]);
ac.build();
memset(dp,-1,sizeof(dp));
dp[0][0]=0;
int state=ac.L;
ll maxcost=-1;
int len,goodstate;
for(int i=1; i<=n; i++)
for(int j=0; j<state; j++)if(dp[i-1][j]!=-1)
for(int k=0; k<26; k++)
{
int newstate=ac.Next[j][k];
int thecost=ac.end[newstate];
if(dp[i][newstate]<dp[i-1][j]+thecost)
{
dp[i][newstate]=dp[i-1][j]+thecost;
char tmp=k+'a';
rout[i][newstate]=rout[i-1][j]+tmp;
}
else if(dp[i][newstate]==dp[i-1][j]+thecost)
{
char tmp=k+'a';
if(rout[i][newstate]>rout[i-1][j]+tmp)
{
rout[i][newstate]=rout[i-1][j]+tmp;
}
}
if(maxcost<dp[i][newstate])
{
maxcost=dp[i][newstate];
len=i;
goodstate=newstate;
}
else if(maxcost==dp[i][newstate])
{
if(rout[len][goodstate]>rout[i][newstate])
{
len=i;
goodstate=newstate;
}
}
}
if(maxcost==0)
puts("");
else
{
cout<<rout[len][goodstate]<<endl;
}
}
return 0;
}
M - Resource Archiver
先用AC自動機建好狀態,然後對於每個end不為0的節點求出它到其他節點的最短距離,然後進行dp即可。dp[i][j] 代表包含串的狀態為i,目前在的狀態為j的能組成符合題意的最短串長。
#include<bits/stdc++.h>
using namespace std;
const int maxn=60005;
const int inf=0x3f3f3f3f;
int state[15],cnt;
int d[15][15];
int dis[maxn];
struct Trie
{
int Next[maxn][2],fail[maxn],end[maxn];
bool virus[maxn];
int root,L;
int newnode()
{
Next[L][0]=Next[L][1]=-1;
end[L]=0;
virus[L++]=0;
return L-1;
}
void init()
{
L=0;
root=newnode();
}
void insert(char s[],int type)
{
int len=strlen(s);
int now=root;
for(int i=0;i<len;i++)
{
int go=s[i]-'0';
if(Next[now][go]==-1)
Next[now][go]=newnode();
now=Next[now][go];
}
if(type>=0)
end[now]|=(1<<type);
else
virus[now]=1;
}
void build()
{
int now=root;
queue<int>Q;
for(int i=0;i<2;i++)
{
if(Next[now][i]==-1)
Next[now][i]=root;
else
{
fail[Next[now][i]]=root;
Q.push(Next[now][i]);
}
}
while(!Q.empty())
{
now=Q.front();Q.pop();
end[now]|=end[fail[now]];
virus[now]|=virus[fail[now]];
for(int i=0;i<2;i++)
{
if(Next[now][i]==-1)
Next[now][i]=Next[fail[now]][i];
else
{
fail[Next[now][i]]=Next[fail[now]][i];
Q.push(Next[now][i]);
}
}
}
}
void buildstate()
{
state[0]=0;
cnt=1;
for(int i=0;i<L;i++)if(!virus[i]&&end[i])
state[cnt++]=i;
}
void bfs(int s)
{
int thestate=state[s];
queue<int>Q;
Q.push(thestate);
memset(dis,-1,sizeof(dis));
dis[thestate]=0;
while(!Q.empty())
{
int x=Q.front();Q.pop();
for(int i=0;i<2;i++)
{
int newstate=Next[x][i];
if(virus[newstate])continue;
if(dis[newstate]==-1)
{
dis[newstate]=dis[x]+1;
Q.push((newstate));
}
}
}
for(int i=0;i<cnt;i++)
d[s][i]=dis[state[i]];
}
void solve()
{
for(int i=0;i<cnt;i++)
bfs(i);
}
};
Trie ac;
int dp[(1<<10)][15];
int n,m;
int dfs(int strsta,int nowsta)
{
if(strsta==(1<<n)-1)return 0;
if(dp[strsta][nowsta]!=inf)return dp[strsta][nowsta];
for(int i=0;i<=cnt;i++)
{
if(d[nowsta][i]<0)continue;
int newstrsta=strsta|ac.end[state[i]];
if(newstrsta==strsta)continue;
dp[strsta][nowsta]=min(dp[strsta][nowsta],dfs(newstrsta,i)+d[nowsta][i]);
}
return dp[strsta][nowsta];
}
int main()
{
while(~scanf("%d %d",&n,&m)&&n)
{
char s[1005];
ac.init();
for(int i=0;i<n;i++)
{
scanf("%s",s);
ac.insert(s,i);
}
for(int i=0;i<m;i++)
{
scanf("%s",s);
ac.insert(s,-1);
}
ac.build();
ac.buildstate();
ac.solve();
memset(dp,inf,sizeof(dp));
printf("%d\n",dfs(0,0));
}
return 0;
}
N - BCD Code
這題思路挺好想的,可惜中間寫錯了,還找不出來。。處理A的時候應該對字元減減就行,而我卻減’1’。。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2505;
const int mod=1000000009;
struct Trie
{
int Next[2010][2],fail[2010];
bool end[2010];
int root,L;
int newnode()
{
for(int i = 0;i < 2;i++)
Next[L][i] = -1;
end[L++] = false;
return L-1;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char buf[])
{
int len = strlen(buf);
int now = root;
for(int i = 0;i < len ;i++)
{
if(Next[now][buf[i]-'0'] == -1)
Next[now][buf[i]-'0'] = newnode();
now = Next[now][buf[i]-'0'];
}
end[now] = true;
}
void build()
{
queue<int>Q;
fail[root] = root;
for(int i = 0;i < 2;i++)
if(Next[root][i] == -1)
Next[root][i] = root;
else
{
fail[Next[root][i]] = root;
Q.push(Next[root][i]);
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
if(end[fail[now]])end[now] = true;
for(int i = 0;i < 2;i++)
if(Next[now][i] == -1)
Next[now][i] = Next[fail[now]][i];
else
{
fail[Next[now][i]] = Next[fail[now]][i];
Q.push(Next[now][i]);
}
}
}
};
Trie ac;
int bcd[maxn][10];
int change(int pre,int num)
{
if(ac.end[pre])return -1;
int cur = pre;
for(int i = 3;i >= 0;i--)
{
if(ac.end[ac.Next[cur][(num>>i)&1]])return -1;
cur = ac.Next[cur][(num>>i)&1];
}
return cur;
}
void pre_init()
{
for(int i = 0;i <ac.L;i++)
for(int j = 0;j <10;j++)
bcd[i][j] = change(i,j);
}
int a[210];
ll dp[210][maxn];
long long dfs(int pos,int s,bool flag,bool z)
{
if(pos == -1)return 1;
if(!flag && dp[pos][s]!=-1)return dp[pos][s];
long long ans = 0;
if(z)
{
ans += dfs(pos-1,s,flag && a[pos]==0,true);
ans %= mod;
}
else
{
if(bcd[s][0]!=-1)ans += dfs(pos-1,bcd[s][0],flag && a[pos]==0,false);
ans %= mod;
}
int end = flag?a[pos]:9;
for(int i = 1;i<=end;i++)
{
if(bcd[s][i]!=-1)
{
ans += dfs(pos-1,bcd[s][i],flag&&i==end,false);
ans %=mod;
}
}
if(!flag && !z)dp[pos][s] = ans;
return ans;
}
long long solve(char s[])
{
int len = strlen(s);
for(int i = 0;i < len;i++)
a[i] = s[len-1-i]-'0';
return dfs(len-1,0,1,1);
}
void change(char s[])
{
int slen=strlen(s);
for(int i=slen-1;i>=0;i--)
{
if(s[i]>='1')
{
s[i]-='1';
break;
}
else
s[i]='9';
}
}
char str[210];
int main()
{
int T;
scanf("%d",&T);
int N;
while(T--)
{
ac.init();
scanf("%d",&N);
for(int i=1; i<=N; i++)
{
scanf("%s",str);
ac.insert(str);
}
ac.build();
pre_init();
memset(dp,-1,sizeof(dp));
int ans=0;
scanf("%s",str);
int len = strlen(str);
for(int i = len -1;i >=0;i--)
{
if(str[i]>'0')
{
str[i]--;
break;
}
else str[i] = '9';
}
ans-=solve(str);
ans%=mod;
scanf("%s",str);
ans+=solve(str);
ans%=mod;
if(ans<0)ans+=mod;
printf("%d\n",ans);
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=205;
const int mod=1000000007;
typedef long long ll;
struct Trie
{
int Next[maxn][2],fail[maxn],end[maxn];
int root,L;
int newno