这两天又刷了两道AC自动机的题:突然发现uva上对数组越界的判断居然是TLE(T_T惨痛的经历,希望以后不会有人和我一样惨)。
还有就是ac自动机裸考的题真的不多,都是与其他的算法结合到一起的。
第一道 uva 11468
大致题意:给出一些字符和各自对应的选择概率,随机选择L次后将得到一个长度为L的随机字符串S(每次独立随机)。给出K个模板串,计算S不包含任何一个串的概率(即任何一个模板串都不是S的连续子串)。
题解:将所有的模板生成一个ac自动机,并且将哪些虚拟边与真实边一视同仁;在代码中的修改就是将上回我给出的模板中的if (!u)continue;改为 if (!u){ch[r][c]=ch[f[r]][c];continue;} 这样我们就不需要失配函数了,不用维护val与last数组了。
建立完上述ac自动机后(你也可以把上述ac自动机看成一个图),我们从0节点走,并且把所有单词节点标记为“禁止”。于是就变成了求从节点0开始走L步,不进入任何禁止的节点的概率。这个可以用记忆化搜索的方法求得,设d(i,j)表示当前在节点i,还需要走j步,不碰到任何禁止节点的概率,代码如下:
double getProb(int u,int L){
if (!L) return 1.0;
if (vis[u][L]) return d[u][L];
vis[u][L]=true;
double &ans=d[u][L];
ans=0.0;
for (int i=0;i
// printf("%d %d %d\n",u,i,val[ch[u][i]]);
ans+=prob[i]*getProb(ch[u][i],L-1);}
}
//printf("u=%d L=%d %lf\n",u,L,ans);
return ans;
}
我的代码:
#include
#include
#include
const int maxnode= 20*20+100;
const int sigma_size=64;
using namespace std;
double prob[sigma_size];
int idx(char c){
if (c>='a'&&c<='z') return c-'a';
if (c>='A'&&c<='Z') return c-'A'+26;
if (c>='0'&&c<='9') return c-'0'+52;
}
struct AC{
int ch[maxnode][sigma_size];
int val[maxnode];
int fail[maxnode];
bool vis [maxnode][110];
double d[maxnode][110];
int sz;
void clear(){sz=1;memset(ch[0],0,sizeof(ch[0]));memset(vis,0,sizeof(vis));memset(prob,0,sizeof(prob));}
void insert(char *s,int v){
int u=0,n=strlen(s);
for (int i=0;i
//printf("sz=%d\n",sz);
if (!ch[u][c]){
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
ch[u][c]=sz++;
//printf("sz=%d*\n",sz);
}
u=ch[u][c];
}
val[u]=v;
}
void getFail(){
queue
fail[0]=0;
for (int i=0;i
}
while (!qqq.empty()){
int r=qqq.front();qqq.pop();
for (int c=0;c
if (!u){ch[r][c]=ch[fail[r]][c];continue;}
qqq.push(u);
int v=fail[r];
while (v&&!ch[v][c]) v=fail[v];
fail[u]=ch[v][c];
val[u]|=val[fail[u]];
}
}
}
double getProb(int u,int L){
if (!L) return 1.0;
if (vis[u][L]) return d[u][L];
vis[u][L]=true;
double &ans=d[u][L];
ans=0.0;
for (int i=0;i
ans+=prob[i]*getProb(ch[u][i],L-1);}
}
return ans;
}
};
struct AC ac;
int main (){
int T,kase,l;scanf("%d",&T);
for (kase=1;kase<=T;kase++){
ac.clear();
int n,m;scanf("%d",&n);
for (int i=0;i
ac.insert(s,1);
}
scanf("%d",&m);
for (int i=0;i
scanf("%lf",&prob[idx(c[0])]);
}
ac.getFail();
scanf("%d",&l);
printf("Case #%d: %.6lf\n",kase,ac.getProb(0,l));
}
return 0;
}
第二道 uva 11019
题目大意: 给出一个n*m的字符矩阵T,你的任务是找出给定的x*y的字符矩阵P出现了多少次。即需要在二维文本串T中查找二维模板串P。
解题思路:将给定的P矩阵,每一行为一个字符串建立一个ac自动机,然后在遍历字符矩阵T的每一行寻找所有的匹配点。最后我们建立一个count数组,count[r][c]表示T中以(r,c)为左上角,与P等大的矩阵中有多少个完整的行与P对应位置的行完全相同。
当遍历完T矩阵后,计算count[r][c]==x(x为P的行数)的个数,即为我们所求。
我的代码:
#include
#include
#include
#include
#include