「kuangbin带你飞」专题十七 AC自动机
Posted luowentao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「kuangbin带你飞」专题十七 AC自动机相关的知识,希望对你有一定的参考价值。
layout: post
title: 「kuangbin带你飞」专题十七 AC自动机
author: "luowentaoaa"
catalog: true
tags:
- kuangbin
- 字符串
- AC自动机
A.HDU2222 Keywords Search
模板题。给出N个单词,后给你一个长串,问长串中有几个单词。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=500010;
struct Trie{
int next[maxm][26],fail[maxm],end[maxm];
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 buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]++;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;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();
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]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
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<26;j++)
printf("%2d",next[i][j]);
printf("]
");
}
}
};
char buf[maxn];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
cout<<ac.query(buf)<<endl;
}
return 0;
}
B.HDU2896 病毒侵袭
和A题一样的模板题,不过要求我们计算长串中出现了那些字符串,在ac自动机的end数组改成是哪一个字符串的编号然后在查询的时候用used数组记录一下既可。
注意是字符串不仅仅包括字母所以数组开开到128
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=500010;
struct Trie{
int next[maxm][128],fail[maxm],end[maxm];
bool used[550];
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 id){
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]=id;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<128;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();
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]);
}
}
}
bool query(char buf[],int n,int id){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<=n;i++)used[i]=false;
bool flag=false;
for(int i=0;i<len;i++){
now=next[now][buf[i]];
int temp=now;
while(temp!=root){
if(end[temp]){
used[end[temp]]=true;
flag=true;
}
temp=fail[temp];
}
}
if(!flag)return false;
else{
cout<<"web "<<id<<":";
for(int i=1;i<=n;i++)
if(used[i])cout<<" "<<i;
cout<<endl;
return true;
}
}
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<26;j++)
printf("%2d",next[i][j]);
printf("]
");
}
}
};
char buf[maxn];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
int n;
cin>>n;
ac.init();
for(int i=1;i<=n;i++){
cin>>buf;
ac.insert(buf,i);
}
int m;
ac.build();
cin>>m;
int sum=0;
for(int i=1;i<=m;i++){
cin>>buf;
if(ac.query(buf,n,i))sum++;
}
cout<<"total: "<<sum<<endl;
return 0;
}
C.HDU3065 病毒侵袭持续中
这题和B题一样的题意只不过题面很坑
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e8+50;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=500010;
char word[1100][55];
struct Trie{
int next[maxm][128],fail[maxm],end[maxm];
int used[1100];
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 id){
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]=id;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<128;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();
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]);
}
}
}
bool query(char buf[],int n){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<=n;i++)used[i]=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]];
int temp=now;
while(temp!=root){
if(end[temp]){
used[end[temp]]++;
}
temp=fail[temp];
}
}
for(int i=1;i<=n;i++){
if(used[i]){
cout<<word[i]<<": "<<used[i]<<endl;
}
}
}
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("]
");
}
}
};
char buf[maxn];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int t;
int n;
while(cin>>n){
ac.init();
for(int i=1;i<=n;i++){
cin>>word[i];
ac.insert(word[i],i);
}
int m;
ac.build();
// ac.debug();
cin>>buf;
ac.query(buf,n);
}
return 0;
}
D.ZOJ3430 Detect the Virus
题意
给你N个病毒(大小不超过64bytes子串)然后给你M个长串(大小不超过2048byte)问你长串没处理中有几个没处理的子串;
注意:
这里的子串和长串都是经过题目要求处理后的串
处理要求是原本的串的每个字符都是二进制长度为八位,然后合并在一起六位二进制组成一个新的字符,如果末尾缺了两位就添加一个‘=‘字符
例如:假设ab的八位二进制为10101000 01100101
现在合并成101010 000110 0101
变成(F,P,E)
因为后面的0101缺少了两位所以补一个‘=‘所以最终字符串为FPE=;
解法
这题的难点在于把字符串根据题目要求模拟成变化前的样子,然后就是AC自动机板子题;
我们可以把开始的字符串去掉‘=‘然后把字符串的每个字符变成数字(这样好变成二进制的数字处理)然后把
1.第一个数字取后面六位,第二个数字取前面两位;
2.第二个数字取后面四位,第三个数字取前面四位;
3.第三个数字取后面两位,第四个数字取前面六位;
这样就四个数字一组组成三个八位的数字;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2048*10;
const ll inf=0x3f3f3f3f3f3f;
const int maxm=512*64;
struct Trie{
int next[maxm][260],fail[maxm],end[maxm];
int used[1100];
int root,L;
int newnode(){
for(int i=0;i<260;i++)
next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(int buf[],int id,int len){
//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]=id;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<260;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();
for(int i=0;i<260;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(int buf[],int n,int len){
//int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<=n;i++)used[i]=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]];
int temp=now;
while(temp!=root){
if(end[temp]){
used[end[temp]]++;
}
temp=fail[temp];
}
}
int sum=0;
for(int i=1;i<=n;i++){
if(used[i]){
sum++;
}
}
cout<<sum<<endl;
}
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<260;j++)
printf("%2d",next[i][j]);
printf("]
");
}
}
};
int fun(char ch){
if(ch>='A'&&ch<='Z')return ch-'A';
if(ch>='a'&&ch<='z')return ch-'a'+26;
if(ch>='0'&&ch<='9')return ch-'0'+52;
if(ch=='+')return 62;
if(ch=='/')return 63;
}
char buf[maxn];
int aa[maxn];
int after[maxn];
int gao(int n){
int t=0; ///00011010->01101000
for(int i=0;i<n;i+=4){ ///00000110->00000000 按照题意合并成八位的01101000
aa[t++]=((after[i]<<2)|(after[i+1]>>4));///取前一个的后六位 和后一个数的前两位 组成一个新的八位字符
///因为前面i把i+1的前两个取走了,所有i+1现在只有六个中的后四个和i+2的前四个匹配;
if(i+2<n)aa[t++]=((after[i+1]<<4&0xff)|(after[i+2]>>2));
///因为前面i+1把i+2的前四个取走了,所有i+2现在只有六个中的两个和i+2的前六个匹配;
if(i+3<n)aa[t++]=((after[i+2]<<6&0xff)|(after[i+3]));
}
return t;
}
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
int n;
while(cin>>n){
ac.init();
//cout<<"haha"<<endl;
for(int i=1;i<=n;i++){
cin>>buf;
// cout<<"here:?"<<endl;
int len=strlen(buf);
while(buf[len-1]=='=')len--;
for(int j=0;j<len;j++)after[j]=fun(buf[j]);
int k=gao(len);
ac.insert(aa,i,k);
}
int m;
ac.build();
cin>>m;
while(m--){
cin>>buf;
int len=strlen(buf);
while(buf[len-1]=='=')len--;
for(int j=0;j<len;j++)after[j]=fun(buf[j]);
int k=gao(len);
ac.query(aa,n,k);
}
cout<<endl;
}
return 0;
}
E.POJ2778 DNA Sequence
题意
给你N个病毒,问你有长度为L的DNA中有多少种是没有病毒的
思路
所有病毒串构造一个AC自动机,这个AC自动机可以看作一张有向图,图上的每个顶点就是Trie树上的结点,每个结点都可以看作是某个病毒串的前缀,Trie树的根则是空字符串。
而从根出发,在AC自动机上跑,经过k次转移到达某个结点,这个结点所代表的病毒串前缀可以看作长度为k的字符串的后缀,如果接下去跑往ATCG四个方向转移,就能到达新的结点,转移到新的长k+1字符串的后缀。
这样带着一个后缀状态的转移就能绕开病毒串,所以病毒串末尾的结点要标记,后缀存在病毒串的结点也要标记(这个在计算结点fail的时候就能处理),转移时就不能转移到被标记的结点。
接下来,题目的数据范围是10个长度10的病毒串,所以Trie树中最多101左右个结点,那么AC自动机整个转移就可以构建一张101*101的邻接矩阵,矩阵i行j列的权值是结点i转移到结点j的方案数。
而进行k次转移,从结点i转移到结点j的方案数是这个矩阵的k次幂,这个结论离散数学的图论有..
所以,长度n的字符串的方案数,就是转移n次根结点能到所有结点的方案和就是答案。就是计算矩阵的n次幂,统计根所在行的数字和,n的达到20亿用矩阵快速幂即可。
对于agc、c而言,如果我zou过 a-g-c-d 这个路径。
root
/ a c
/
g
/
c
/
d
由上面这个图可知 左边的d 和 右边的c都是危险节点。 但漏掉了左边上的c
所以如果fail指针指向那个节点是危险节点的话,那么当前节点也是危险节点
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
const ll mod=100000;
const int maxn=10*10+5;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct Matrix{
unsigned long long mat[maxn][maxn];
int n;
Matrix(){}
Matrix(int _n){
n=_n;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)mat[i][j]=0;
}
Matrix operator *(const Matrix &b)const{
Matrix ret=Matrix(n);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
ret.mat[i][j]+=(mat[i][k]*b.mat[k][j])%mod;
ret.mat[i][j]%=mod;
}
}
}
return ret;
}
unsigned long long pow_m(unsigned long long a,int n){
unsigned long long ret=1,tmp=a;
while(n){
if(n&1)ret*=tmp;
tmp*=tmp;
n>>=1;
}return ret;
}
Matrix pow_M(Matrix a,ll n){
Matrix ret=Matrix(a.n);
for(int i=0;i<a.n;i++)ret.mat[i][i]=1;
Matrix tmp=a;
while(n){
if(n&1)ret=ret*tmp;
tmp=tmp*tmp;
n>>=1;
}
return ret;
}
};
struct Trie{
int next[maxn][4],fail[maxn],id['Z'+1];
bool end[maxn];
int root,L;
int newnode(){
for(int i=0;i<4;++i)next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
id['A']=0;id['T']=1;id['C']=2;id['G']=3;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][id[buf[i]]]==-1)next[now][id[buf[i]]]=newnode();
now=next[now][id[buf[i]]];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<4;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<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]);
}
}
}
Matrix getMatrix(){
Matrix ret=Matrix(L);
for(int i=0;i<L;i++){
for(int j=0;j<4;j++){
if(end[next[i][j]]==false&&end[i]==false)
ret.mat[i][next[i][j]]++;
}
}
return ret;
}
};
Trie ac;
char buf[15];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
ll L;
cin>>n>>L;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
Matrix mat=ac.getMatrix();
mat=mat.pow_M(mat,L);
ll ans=0;
for(int i=0;i<mat.n;i++){
ans=(ans+mat.mat[0][i])%mod;
}
cout<<ans<<endl;
return 0;
}
F.HDU2243 考研路茫茫――单词情结
题意
跟上题一样,不过这题是让你求包括的个数,而且不仅仅是长度为L而且长度小于L的也要包括进去;可以通过上题很快求出不包括的,然后求和,在求出全部的26^1+...26^L的个数减去不包括的就是答案;
思路
通过矩阵求出全部长度的可能和AC自动机求出来的矩阵的和,然后相减
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll mod=2147483648LL;
const ll maxn=5*6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct Matrix{
unsigned long long mat[maxn][maxn];
int n;
Matrix(){}
Matrix(int _n){
n=_n;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)mat[i][j]=0;
}
Matrix operator *(const Matrix &b)const{
Matrix ret=Matrix(n);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
ret.mat[i][j]+=(mat[i][k]*b.mat[k][j]);
}
}
}
return ret;
}
unsigned long long pow_m(unsigned long long a,int n){
unsigned long long ret=1,tmp=a;
while(n){
if(n&1)ret*=tmp;
tmp*=tmp;
n>>=1;
}return ret;
}
Matrix pow_M(Matrix a,ll n){
Matrix ret=Matrix(a.n);
for(int i=0;i<a.n;i++)ret.mat[i][i]=1;
Matrix tmp=a;
while(n){
if(n&1)ret=ret*tmp;
tmp=tmp*tmp;
n>>=1;
}
return ret;
}
};
struct Trie{
int next[maxn][26],fail[maxn];
bool end[maxn];
int root,L;
int newnode(){
for(int i=0;i<26;++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]-'a']==-1)next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;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<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]);
}
}
}
Matrix getMatrix(){
Matrix ret=Matrix(L+1);
for(int i=0;i<L;i++){
for(int j=0;j<26;j++){
if(end[next[i][j]]==false)
ret.mat[i][next[i][j]]++;
}
}
for(int i=0;i<L+1;i++)ret.mat[i][L]=1;
return ret;
}
};
Trie ac;
char buf[15];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
ll L;
while(cin>>n>>L){
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
Matrix mat=ac.getMatrix();
mat=mat.pow_M(mat,L);
ull res=0;
for(int i=0;i<mat.n;i++)res+=mat.mat[0][i];
res--;
ull ans=0;
Matrix all=Matrix(2);all.mat[0][0]=26;all.mat[0][1]=all.mat[1][1]=1;
/*
26 1 × Sn -> Sn+1
0 1 × 26 -> 26
*/
all=all.pow_M(all,L);
ans=all.mat[0][1]*26;
cout<<ans-res<<endl;
}
return 0;
}
G.POJ - 1625 Censored!
题意
n,m,p;n代表总共有n个字母,m代表字符串的长度为m,p代表病毒字符串的个数;题目让你求的是不包含病毒的字符串长度为m的个数为多少。
题解
较麻烦的题,但是不要被代码长度吓到了
先讲一讲思路,给出了可用字母和不可用单词,
那么首先想到将不可用单词建立AC自动机。
那么什么情况下会用到禁止使用的单词呢?
我们将Trie树中间被标记的点叫做危险结点。
在AC自动机中不断遍历,如果遇到危险结点,就是不合法的
考虑如何遍历,这里需要用到AC自动机建立失败指针的一个技巧:
若当前结点无儿子结点,把失败指针处的儿子拿过来。
如果当前结点有儿子结点,把失败指针处的危险标记拿过来,详细见代码。
这样就方便了后面找后继结点。
不过没有取模操作,需要大数。
大数不可以矩阵快速幂吗?内存不够……
使用dp递推,递推公式也比较简单。
#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <bitset>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
ll gcd(ll a,ll b){while(b){ll t=a%b;a=b;b=t;}return a;}
ll lcm(ll a,ll b){return a*b/__gcd(a,b);}
map<char,int>mp;
int N,M,P;
struct Matrix{
int mat[110][110];
int n;
Matrix(){}
Matrix(int _n){
n=_n;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)mat[i][j]=0;
}
};
struct Trie{
int next[110][256],fail[110];bool end[110];
int L,root;
int newnode(){
for(int i=0;i<256;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][mp[buf[i]]]==-1)
next[now][mp[buf[i]]]=newnode();
now=next[now][mp[buf[i]]];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<256;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]]==true)end[now]=true;
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]);
}
}
}
Matrix getMatrix(){
Matrix res=Matrix(L);
for(int i=0;i<L;i++)
for(int j=0;j<N;j++)
if(end[next[i][j]]==false)res.mat[i][next[i][j]]++;
return res;
}
};
struct BigInt{
const static int mod=10000;
const static int DLEN=4;
int a[600],len;
BigInt(){
memset(a,0,sizeof(a));
len=1;
}
BigInt(int v){
memset(a,0,sizeof(a));
len=0;
do{
a[len++]=v%mod;
v/=mod;
}while(v);
}
BigInt(const char s[]){
memset(a,0,sizeof(a));
int L=strlen(s);
len=L/DLEN;
if(L%DLEN)len++;
int index=0;
for(int i=L-1;i>=0;i-=DLEN){
int t=0;
int k=i-DLEN+1;
if(k<0)k=0;
for(int j=k;j<=i;j++)
t=t*10+s[j]-'0';
a[index]=t;
}
}
BigInt operator +(const BigInt &b)const{
BigInt res;
res.len=max(len,b.len);
for(int i=0;i<res.len;i++){
res.a[i]+=((i<len)?a[i]:0)+((i<b.len)?b.a[i]:0);
res.a[i+1]+=res.a[i]/mod;
res.a[i]%=mod;
}
if(res.a[res.len]>0)res.len++;
return res;
}
BigInt operator *(const BigInt &b)const
{
BigInt res;
for(int i = 0; i < len;i++)
{
int up = 0;
for(int j = 0;j < b.len;j++)
{
int temp = a[i]*b.a[j] + res.a[i+j] + up;
res.a[i+j] = temp%mod;
up = temp/mod;
}
if(up != 0)
res.a[i + b.len] = up;
}
res.len = len + b.len;
while(res.a[res.len - 1] == 0 &&res.len > 1)res.len--;
return res;
}
void output()
{
printf("%d",a[len-1]);
for(int i = len-2;i >=0 ;i--)
printf("%04d",a[i]);
printf("
");
}
};
char buf[1010];
BigInt dp[2][110];
Trie ac;
int main()
{
while(scanf("%d%d%d",&N,&M,&P)==3){
gets(buf);
gets(buf);
mp.clear();
int len=strlen(buf);
for(int i=0;i<len;i++)mp[buf[i]]=i;
ac.init();
for(int i=0;i<P;i++){
gets(buf);
ac.insert(buf);
}
ac.build();
Matrix a=ac.getMatrix();
int now=0;
dp[now][0]=1;
for(int i=1;i<a.n;i++)dp[now][i]=0;
for(int i=0;i<M;i++){
now^=1;
for(int j=0;j<a.n;j++)dp[now][j]=0;
for(int j=0;j<a.n;j++){
for(int k=0;k<a.n;k++){
if(a.mat[j][k]>0){
dp[now][k]=dp[now][k]+dp[now^1][j]*a.mat[j][k];
}
}
}
}
BigInt ans=0;
for(int i=0;i<a.n;i++)ans=ans+dp[now][i];
ans.output();
}
return 0;
}
H.HDU - 2825 Wireless Password
题意
给你一堆(不超过10个)长度不超过10的单词,然后问你长度为N句子并且包含K个不同单词的个数有多少个;
题解
先利用ac自动机求出各种状态,(总共不超过10*10)个,然后dp枚举长度,状态,和已有个数,已有个数可以用状态压缩O(1)求出来
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=(1<<10)+5;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[110][110][maxn];
struct Trie{
int next[110][26],fail[110],end[110];
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 buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]|=(1<<id);
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;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();
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]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
};
Trie ac;
int num[maxn];
int slove(int n,int m,int k){
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=0;i<n;i++){//长度
for(int j=0;j<ac.L;j++){//状态
for(int l=0;l<(1<<m);l++){//个数
if(dp[i][j][l]){
for(int p=0;p<26;p++){
int nowj=ac.next[j][p];
int nowl=l|(ac.end[nowj]);
dp[i+1][nowj][nowl]=(dp[i+1][nowj][nowl]+dp[i][j][l])%mod;
}
}
}
}
}
ll sum=0;
for(int i=0;i<ac.L;i++){
for(int j=0;j<(1<<m);j++){
if(num[j]>=k){
sum=(sum+dp[n][i][j])%mod;
}
}
}
return sum;
}
int n,m,k;
char buf[150];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
memset(num,0,sizeof(num));
for(int i=0;i<maxn;i++){
for(int j=0;j<=10;j++){
if(i&(1<<j))num[i]++;
}
}
while(cin>>n>>m>>k){
if(n==0&&m==0&&k==0)break;
ac.init();
for(int i=0;i<m;i++){
cin>>buf;
ac.insert(buf,i);
}
ac.build();
cout<<slove(n,m,k)<<endl;
}
return 0;
}
I.HDU - 2296 Ring
题意
给定m个不同的单词,单词由小写字母组成,每个单词都有自身的价值。现在你要设计一个字符串,字符串的长度不能超过n。如果字符串里面包含了某个单词,就可以获得相应的价值,价值可以重复计算。单词可以相互重叠。
题解
模板AC自动机,不过加了个字符串储存 不过不知道这是不是就是传说中的trie图
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=(1<<10)+5;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[55][1100];
string ss[55][1100];
int money[150];
int cmp(string a,string b){
if(a.length()==b.length())return a<b;
else return a.length()<b.length();
}
int mycmp(string a,string b){
if(a.length()==b.length())return a<b;
else return a.length()<b.length();
}
struct Trie{
int next[1100][26],fail[1010],end[1010];
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 buf[],int id,int money){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]=money;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;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();
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]);
}
}
}
int query(char buf[]){
int len=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res;
}
string slove(int n,int m){
for(int i=0;i<=n;i++)for(int j=0;j<L;j++)ss[i][j]="";
dp[0][0]=0;
int ma=0;
for(int i=0;i<n;i++){
for(int j=0;j<L;j++){
if(dp[i][j]!=-inf){
for(int k=0;k<26;k++){
if(next[j][k]!=-1){
int nex=next[j][k];
string nowstr=ss[i][j]+char(k+'a');
int nowmoney=dp[i][j]+end[nex];
if(nowmoney>dp[i+1][nex]||(nowmoney==dp[i+1][nex]&&mycmp(nowstr,ss[i+1][nex]))){
dp[i+1][nex]=nowmoney;
ss[i+1][nex]=nowstr;
if(dp[i+1][nex]>ma){
ma=dp[i+1][nex];
}
}
}
}
}
}
}
vector<string>ve;
for(int i=0;i<=n;i++){
for(int j=0;j<L;j++){
if(dp[i][j]==ma){
ve.push_back(ss[i][j]);
}
}
}
sort(ve.begin(),ve.end(),cmp);
return ve[0];
}
};
Trie ac;
int n,m,k;
char buf[150][150];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
cin>>n>>m;
ac.init();
for(int i=0;i<m;i++)cin>>buf[i];
for(int i=0;i<m;i++)cin>>money[i],ac.insert(buf[i],i,money[i]);
memset(dp,-inf,sizeof(dp));
ac.build();
cout<<ac.slove(n,m)<<endl;
}
return 0;
}
J.HDU - 2457 DNA repair
题意
给你N个病毒串,然后给你一个长度为N的基因串,让你替换成没有病毒的基因串,求最小的替换次数(每次只能替换一个字符)
题解
DP I ,J I: 当前串的长度,J,结尾的位置
直接DP扫过去,用AC自动机构造出病毒的节点,然后开始构造,如果新的点和原来的串相同就
[
DP[i][j]=min(DP[i-1][j],dp[i][j])
]
否则
[
DP[i][j]=min(dp[i-1][j]+1,dp[i][j])
]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=(1<<10)+5;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[1050][1050];
char buf[1500];
struct Trie{
int next[1100][4],fail[1010];
bool end[1010];
char s['Z'+1];
int root,L;
int newnode(){
for(int i=0;i<4;i++)
next[L][i]=-1;
end[L++]=false;
return L-1;
}
void init(){
L=0;
s['A']=0;s['G']=1;s['T']=2;s['C']=3;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][s[buf[i]]]==-1)
next[now][s[buf[i]]]=newnode();
now=next[now][s[buf[i]]];
}
end[now]=true;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<4;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<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]);
}
}
}
void slove(int n){
memset(dp,inf,sizeof(dp));
dp[0][0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<L;j++){
if(dp[i][j]!=inf){
for(int k=0;k<4;k++){
int nex=next[j][k];
if(end[nex])continue;
if(s[buf[i]]==k){
dp[i+1][nex]=min(dp[i+1][nex],dp[i][j]);
}
else{
dp[i+1][nex]=min(dp[i+1][nex],dp[i][j]+1);
}
}
}
}
}
int mi=inf;
for(int i=0;i<L;i++){
if(dp[n][i]<mi)mi=dp[n][i];
}
if(mi==inf)cout<<-1<<endl;
else cout<<mi<<endl;
}
};
Trie ac;
int n;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t=0;
while(cin>>n){
if(n==0)break;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
cout<<"Case "<<++t<<": ";
int len=strlen(buf);
ac.slove(len);
}
return 0;
}
K.ZOJ - 3228 Searching the String
题意
先给你一个字符串,然后给你若干个子串,0代表能够重叠,1代表不能重叠,求出如今母串的次数。
题解
多一个推断的是假设这个子串与上次出现的次数大于子串长度的话。就代表这次不是重叠的了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=20090717;
const int maxn=600010;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
char buf[100010];
int ans[maxn][2];
struct Trie{
int next[maxn][26],fail[maxn],deep[maxn],last[maxn];
int end[100010];
int root,L;
int newnode(){
for(int i=0;i<26;i++)
next[L][i]=-1;
deep[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
if(next[now][buf[i]-'a']==-1)
next[now][buf[i]-'a']=newnode();
deep[next[now][buf[i]-'a']]=deep[now]+1;
now=next[now][buf[i]-'a'];
}
end[id]=now;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;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();
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]);
}
}
}
void slove(char buf[]){
for(int i=0;i<L;i++){
ans[i][0]=ans[i][1]=0;
last[i]=-1;
}
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root){
ans[temp][0]++;
if(i-last[temp]>=deep[temp]){
ans[temp][1]++;
last[temp]=i;
}
temp=fail[temp];
}
}
}
};
Trie ac;
int n;
char s[10];
int flag[100010];
void neww(){
int n, cas = 1;
while (scanf("%s", buf) != EOF) {
scanf("%d", &n);
ac.init();
for (int i = 0; i < n; i++) {
scanf("%d%s", &flag[i], s);
ac.insert(s, i);
}
ac.build();
ac.slove(buf);
printf("Case %d
", cas++);
for (int i = 0; i < n; i++)
printf("%d
", ans[ac.end[i]][flag[i]]);
printf("
");
}
}
int main()
{
neww();
return 0;
}
L.HDU - 3341 Lost‘s revenge
题意
给出N个模式串(len<=10)和一个目标长串(len<=40)求将目标串重新排列后所能包含的模式串个数
题解
首先重排列,所以目标串中的ACGT个数不能变化
刚开始想开个五维数组来存取状态,但是空间会爆炸;
所以搜了一下发现可以用模拟进制来把 ACGT的个数用一个数字表达
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=15000;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[505][maxn];
struct Trie{
int next[505][4],fail[505],end[505];
int ok['Z'];
int root,L;
int newnode(){
for(int i=0;i<4;i++)next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;ok['A']=0;ok['G']=1;ok['C']=2;ok['T']=3;
root=newnode();
}
void insert(char buf[]){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=ok[buf[i]];
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}end[now]++;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<4;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();
end[now]+=end[fail[now]];
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]);
}
}
}
int slove(char buf[]){
int len=strlen(buf);
int num[4]={0,0,0,0};
memset(dp,-inf,sizeof(dp));
for(int i=0;i<len;i++){
num[ok[buf[i]]]++;
}
dp[0][0]=0;
int bit[4];
bit[0]=1;
bit[1]=(num[0]+1);
bit[2]=(num[1]+1)*(num[0]+1);
bit[3]=(num[2]+1)*(num[1]+1)*(num[0]+1);
for(int i=0;i<=num[0];i++)
for(int j=0;j<=num[1];j++)
for(int k=0;k<=num[2];k++)
for(int l=0;l<=num[3];l++){
int nowsta=i*bit[0]+j*bit[1]+k*bit[2]+l*bit[3];
for(int p=0;p<L;p++){
if(dp[p][nowsta]!=inf){
for(int kk=0;kk<4;kk++){
if(i==num[0]&&kk==0)continue;
else if(j==num[1]&&kk==1)continue;
else if(k==num[2]&&kk==2)continue;
else if(l==num[3]&&kk==3)continue;
int nexsta=nowsta+bit[kk];
int nex=next[p][kk];
dp[nex][nexsta]=max(dp[nex][nexsta],dp[p][nowsta]+end[nex]);
}
}
}
}
int ans=num[0]*bit[0]+num[1]*bit[1]+num[2]*bit[2]+num[3]*bit[3];
int ma=0;
for(int i=0;i<L;i++)ma=max(dp[i][ans],ma);
return ma;
}
};
char buf[45];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
int cnt=1;
while(cin>>n){
if(n==0)break;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
cout<<"Case "<<cnt++<<": ";
cout<<ac.slove(buf)<<endl;
}
return 0;
}
M.HDU - 3247 Resource Archiver
题意
给定n个文本串,m个病毒串,文本串重叠部分可以合并,但合并后不能含有病毒串,问所有文本串合并后最短多长。
思路
把病毒和文本出串塞入AC自动机中,然后把每个文本串的结点 和另一个文本串的结点的最短路求出来,在用状态DP求出每个点到另一个点之间的距离
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=60000+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct Trie{
int next[maxn][2],fail[maxn];
int end[maxn],dp[1025][11];
int mat[11][11];
int pos[11];
int dis[maxn];
int root,L;
int cnt;
int newnode(){
for(int i=0;i<2;i++)next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=buf[i]-'0';
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}
if(id==-1||end[now]==-1)end[now]=-1;
else end[now]|=(1<<id);
}
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]]==-1)end[now]=-1;
else end[now]|=end[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 bfs(int id){
queue<int>Q;
Q.push(pos[id]);
memset(dis,-1,sizeof(dis));
dis[pos[id]]=0;
while(!Q.empty()){
int now=Q.front();
Q.pop();
for(int i=0;i<2;i++){
if(end[next[now][i]]>=0&&dis[next[now][i]]<0){
dis[next[now][i]]=dis[now]+1;
Q.push(next[now][i]);
}
}
}
for(int i=0;i<cnt;i++)
mat[id][i]=dis[pos[i]];
return;
}
void slove(int n){
pos[0]=0;
cnt=1;
for(int i=0;i<L;i++)
if(end[i]>0)pos[cnt++]=i;
for(int i=0;i<cnt;i++){
bfs(i);
}
for(int i=0;i<(1<<n);i++)
for(int j=0;j<cnt;j++)
dp[i][j]=inf;
dp[0][0]=0;
for(int i=0;i<(1<<n);i++)
for(int j=0;j<cnt;j++)
if(dp[i][j]!=inf){
for(int k=0;k<cnt;k++){
if(mat[j][k]<0)continue;
dp[i|end[pos[k]]][k]=min(dp[i|end[pos[k]]][k],dp[i][j]+mat[j][k]);
}
}
int ans=inf;
for(int i=0;i<cnt;i++)ans=min(ans,dp[(1<<n)-1][i]);
cout<<ans<<endl;
return;
}
};
char buf[55000];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n,m;
while(cin>>n>>m){
if(n==0&&m==0)break;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf,i);
}
for(int i=0;i<m;i++){
cin>>buf;
ac.insert(buf,-1);
}
ac.build();
ac.slove(n);
}
return 0;
}
N.ZOJ - 3494 BCD Code
题意
跟数位DP的一题重复了 这里再做一次
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+9;
const int maxn=2200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
ll dp[300][maxn];
int num[300];
char nu[10][5]={"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001"};
struct Trie{
int next[maxn][2],fail[maxn];
bool end[maxn];
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++){
int ch=buf[i]-'0';
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}
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]);
}
}
}
ll dfs(int pos,int in,bool zero,bool limit){
if(pos==-1)return 1;
if(dp[pos][in]!=-1&&!limit)return dp[pos][in];
int up=limit?num[pos]:9;
ll ans=0;
for(int i=0;i<=up;i++){
int now=in;
if(zero&&i==0)ans=(ans+dfs(pos-1,in,zero&&i==0,limit&&i==up))%mod;
else{
bool flag=0;
for(int j=0;j<4;j++){
int ne=nu[i][j]-'0';
now=next[now][ne];
if(end[now]){
flag=1;break;
}
}
if(!flag)ans=(ans+dfs(pos-1,now,zero&&i==0,limit&&i==up))%mod;
}
}
if(!limit&&!zero)dp[pos][in]=ans;
return ans;
}
ll Ac(char buf[]){
int len=strlen(buf);
for(int i=0;i<len;i++){
num[i]=int(buf[len-1-i]-'0');
}
return dfs(len-1,0,true,true);
}
};
char buf[22];
Trie ac;
char a[250],b[250];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
ac.init();
int n;
cin>>n;
memset(dp,-1,sizeof(dp));
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>a>>b;
int len=strlen(a);
while(a[len-1]=='0'){
a[len-1]='9';
len--;
}
a[len-1]--;
cout<<((ac.Ac(b)-ac.Ac(a)+mod)%mod)<<endl;
}
return 0;
}
O.HDU - 4758 Walk Through Squares
题意
给你一个n*m的网格,现在你要从(1,1)走到(n,m),每次只能向右走或者向下走,走完后会形成一个包含R,D的序列,这个序列必须要包含题目给出的两个字符串,问有多少种方案。
题解
由于要从(1,1)走到(n,m),所以这个形成的字符串包含R和D的数量是一定的。
现在考虑dp i j k sta,表示包含两种串的组合状态,走了i个D,走了j个R,走到了k这个AC自动机的节点的方案数,串1和串2的状态sta 然后dp一下就行了。复杂度为O(3×n×m×len(s1)×len(s2))
按理说应该是N个D M个R 可是这样我就错了,换一下才AC。。。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1010;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int dp[110][110][210][4];
struct Trie{
int next[maxn][2],fail[maxn];
int end[maxn];
int ID['Z'+1];
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;
ID['R']=0;ID['D']=1;
root=newnode();
}
void insert(char buf[],int id){
int len=strlen(buf);
int now=root;
for(int i=0;i<len;i++){
int ch=ID[buf[i]];
if(next[now][ch]==-1){
next[now][ch]=newnode();
}
now=next[now][ch];
}
end[now]=(1<<id);
}
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();
end[now]|=end[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 slove(int n,int m){
memset(dp,0,sizeof(dp));
dp[0][0][0][0]=1;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<L;k++)
for(int sta=0;sta<(1<<2);sta++){
if(dp[i][j][k][sta]){
for(int p=0;p<2;p++){
if(p==0){//右
dp[i+1][j][next[k][p]][sta|end[next[k][p]]]+=dp[i][j][k][sta];
dp[i+1][j][next[k][p]][sta|end[next[k][p]]]%=mod;
}
else{
dp[i][j+1][next[k][p]][sta|end[next[k][p]]]+=dp[i][j][k][sta];
dp[i][j+1][next[k][p]][sta|end[next[k][p]]]%=mod;
}
}
}
}
int ans=0;
for(int i=0;i<L;i++){
ans+=dp[n][m][i][3];
ans%=mod;
}
cout<<ans<<endl;
}
};
char buf[110];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
ac.init();
for(int i=0;i<2;i++){
cin>>buf;
ac.insert(buf,i);
}
ac.build();
ac.slove(n,m);
}
return 0;
}
P.HDU - 4511 小明系列故事――女友的考验
题意中文题
题解
设dp i j 表示到第i个点,自动机状态到j的最小步数,注意的是因为我们是根据不合法的路径构造自动机的,所以我们是不能走到某一条路径的末尾的,根据这点来记录我们的终止条件
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1010;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const double inf=1e20;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
pp p[101];
double dp[55][1000];
double dis(pp a,pp b){
return sqrt((double)(1.0*a.first-b.first)*(double)(1.0*a.first-b.first)+(double)(1.0*a.second-b.second)*(double)(1.0*a.second-b.second));
}
int n;
struct Trie{
int next[maxn][55],fail[maxn];
int end[maxn];
int root,L;
int newnode(){
for(int i=1;i<=n;i++)next[L][i]=-1;
end[L++]=0;
return L-1;
}
void init(){
L=0;
root=newnode();
}
void insert(int buf[],int k){
int now=root;
for(int i=0;i<k;i++){
if(next[now][buf[i]]==-1){
next[now][buf[i]]=newnode();
}
now=next[now][buf[i]];
}
end[now]=1;
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=1;i<=n;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();
end[now]|=end[fail[now]];
for(int i=1;i<=n;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 slove(){
for(int i=1;i<=n;i++)for(int j=0;j<L;j++)dp[i][j]=inf;
dp[1][next[root][1]]=0;
for(int i=1;i<n;i++)
for(int j=0;j<L;j++)
if(dp[i][j]!=inf){
for(int k=i+1;k<=n;k++){
int now=next[j][k];
if(end[now])continue;
dp[k][now]=min(dp[k][now],dp[i][j]+dis(p[i],p[k]));
}
}
double ans=inf;
for(int i=0;i<L;i++)ans=min(ans,dp[n][i]);
if(ans==inf)cout<<"Can not be reached!"<<endl;
else printf("%.2f
",ans);
}
};
int buf[110];
Trie ac;
int main()
{
/*std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);*/
int m;
while(cin>>n>>m){
if(n==0&&m==0)break;
for(int i=1;i<=n;i++)cin>>p[i].first>>p[i].second;
ac.init();
while(m--){
int k;
cin>>k;
for(int i=0;i<k;i++)cin>>buf[i];
ac.insert(buf,k);
}
ac.build();
ac.slove();
}
return 0;
}
Q.附加题1 牛客网暑期ACM多校训练营(第九场)- Typing practice
题意
有n(n<=4)个长度为len的字符串,以及一个长度为len2的操作串。每一次你将选取操作串中长度为i(0<=i<=len2)的前缀,问你最少在这个前缀后加多少个字符,使得新字符串的后缀中能够至少出现这n个字符串中的一个。
题解
因为题目中设计多个串的匹配一个长串的问题,我们可以考虑使用AC自动机进行处理。
再考虑题目中要求我们求出匹配串的后缀凑出模式串的最小长度,因此,我们可以将这样的一个问题转化成:在一个Trie图中,处于第i个结点的字符到达任意一个模式串终点j的最短路。
因此,我们只需要利用AC自动机,将利用失配指针的性质,先将整张Trie图建立出来,并将每一个结点到达任意终点的最短路用bfs求出即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=450000;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct Trie{
int next[maxn][26],fail[maxn];
bool end[maxn];
int ans[maxn];
vector<int>ve[maxn];
set<int>st;
int root,L;
int newnode(){
for(int i=0;i<26;i++)next[L][i]=-1;
ans[L]=inf;
end[L++]=false;
return L-1;
}
void init(){
L=0;
st.clear();
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]-'a']==-1){
next[now][buf[i]-'a']=newnode();
}
now=next[now][buf[i]-'a'];
}
end[now]=true;
ans[now]=0;
st.insert(now);
}
void build(){
queue<int>Q;
fail[root]=root;
for(int i=0;i<26;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;
ans[now]=0;
st.insert(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]);
}
}
}
void slove(char buf[]){
for(int i=0;i<L;i++){
for(int j=0;j<26;j++){
ve[next[i][j]].push_back(i);
}
}
queue<int>q;
for(auto i:st){
q.push(i);
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=0;i<ve[now].size();i++){
if(ans[ve[now][i]]>ans[now]+1){
ans[ve[now][i]]=min(ans[ve[now][i]],ans[now]+1);
q.push(ve[now][i]);
}
}
}
int len=strlen(buf);
stack<int>ss;
int now=root;
cout<<ans[now]<<endl;
for(int i=0;i<len;i++){
if(buf[i]=='-'&&ss.empty()){
now=root;
}
else if(buf[i]=='-'){
now=ss.top();
ss.pop();
}
else{
ss.push(now);
now=next[now][buf[i]-'a'];
}
cout<<ans[now]<<endl;
}
}
};
char buf[110000];
Trie ac;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
cin>>n;
ac.init();
for(int i=0;i<n;i++){
cin>>buf;
ac.insert(buf);
}
ac.build();
cin>>buf;
ac.slove(buf);
return 0;
}
以上是关于「kuangbin带你飞」专题十七 AC自动机的主要内容,如果未能解决你的问题,请参考以下文章
[ An Ac a Day ^_^ ] [kuangbin带你飞]专题四 最短路练习 POJ 2387 Til the Cows Come Home
[ An Ac a Day ^_^ ][kuangbin带你飞]专题四 最短路练习 POJ 2240 Arbitrage spfa求负环