[Pku 3691 1625] 字符串(四) {自动机应用}
{
这一篇文章基于AC自动机
介绍AC自动机的应用
引出AC自动机的优化 有限状态自动机
}
AC自动机实质上是一个图
为了解决某些关于字符串的最优化或是统计问题
我们可以结合AC自动机和动态规划
也就是在自动机上动态规划
先来看一个具体的问题
Pku 3691 http://poj.org/problem?id=3691
题意
给定N个模式串(1 ≤ N ≤ 50) 最大长度为20
一个主串(长最大为1000)
允许涉及的字符为4个 {'A','T','G','C'}
求最少修改几个字符 使主串不包含所有模式串
分析
我们可以考虑一个最简单的算法
搜索 枚举每一位的值 然后比较 复杂度达到了O(4^N)
时间复杂度会太大 考虑如何优化
先看一个样例
模式串{'AT','G'}
那么 无论是G AG AAG AAAG 还是AAAAAAAG
他们的宿命都是一样的 最后与G发生匹配 导致不是可行解
再看AA AC CC CA TA TT TC CT 等等
也是一样的只要不含G 不是AT 都是可行解
我们发现似乎有很多状态都是统一的 这就是搜索低效的原因
那么如何让这些雷同的状态统一起来呢?
考虑到这是一个涉及到多模式串匹配的问题
我们尝试建立AC自动机
为了讨论方便 我们换一个样例
模式串{'AT','TAT','C','GC'} 建立AC自动机如下图
我们发现 很多串在AC自动机上匹配的时候 都走到了同一个节点
譬如 AA GA TAA GAA 等等 都走到了根节点 而GG TG等等 都走到了G节点
特别的 含有模式串的字符串都走到过粉色节点
我们考虑是不是可以用模式串在AC自动机上匹配 最后走到的节点来概括这个字符串的状态呢?
到这里 暂停一下 请思考 能否用有向图上的动态规划来代替搜索?
需要两维来记录状态 Opt[i][j]
其中 第一维代表走到第i步(也就是主串处理到第i位)
第二维代表在AC自动机上走到j节点
记录当前最少修改几个字符使得不包含所有模式串
也就是不能走到任何一个粉红色节点
这个状态用顺推比较方便
也就是用Opt[i][j]+0/1去更新Opt[i+1][Next[j]]
Next[j]就是决策第i+1位是什么的时候 下一步在AC自动机上会走到哪里
有可能通过j节点一步就到 也有可能通过Fail指针回溯 这个和匹配基本类似
具体要不要+1需要看决策修改还是不修改第i+1位
注意决策的时候不要走到粉色节点上!
最后Max{Opt[Length(MainString)][j]}就是答案了
实际上通过设立第一维由第I步推出第I+1步 保证了动态规划的拓扑性质
第二维用字符串在AC自动机上的结束节点 很好的概括了字符串的状态
这就是这个DP的核心思想
给出核心的DP代码
fillchar(opt,sizeof(opt),$3F);
opt[0][root]:=0;
for i:=0 to m-1 do
for j:=1 to tt do
for k:=1 to 4 do
if not d[j]
then begin
p:=j;
while (p<>root)and(s[p][k]=0) do
p:=f[p];
if s[p][k]=0
then p:=root
else p:=s[p][k];
if d[p] then continue;
t:=i+1;
h:=opt[i][j]+1;
if k=tr[c[t]]
then if opt[t][p]>opt[i][j]
then opt[t][p]:=opt[i][j]
else
else if opt[t][p]>h
then opt[t][p]:=h;
end;
ans:=oo;
for i:=1 to tt do
if ans>opt[m][i]
then ans:=opt[m][i];
write('Case ',ca,': ');
完整代码在文章最后
请注意理解Next[j]的含义 再向下阅读
我们在实现上面一个问题的时候 可以注意到一个问题
如果我们确定了从节点j开始具体通过哪一个决策来转移 Next[j]是固定的
我们萌生了一个优化的思路 先预处理出所有决策K对应的Next[j]的边
对于上面的AC自动机(Fail全指向了根 省略)我们可以构建出这样的图
实际上就是在构建AC自动机的时候如果某个节点的儿子不存在
就通过Fail指针回溯 直到有长辈节点这样的儿子存在或到根节点为止
把本来不存在的儿子指针指向某个长辈的儿子或者根即可
核心的构建代码如下
h:=1; t:=0;
f[root]:=root;
for i:=1 to 4 do
if s[root][i]<>0
then begin
inc(t);
q[t]:=s[root][i];
f[q[t]]:=root;
end
else s[root][i]:=root;
while h<=t do
begin
for i:=1 to 4 do
if s[q[h]][i]<>0
then begin
inc(t);
q[t]:=s[q[h]][i];
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then f[q[t]]:=root
else begin
if d[s[p][i]]=1
then d[q[t]]:=1;
f[q[t]]:=s[p][i];
end;
end
else begin
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then s[q[h]][i]:=root
else s[q[h]][i]:=s[p][i];
end;
inc(h);
end;
这样我们的DP过程就可以大大的简化
不用再每次回溯了
fillchar(opt,sizeof(opt),$3F);
opt[0][1]:=0;
for i:=0 to m-1 do
for j:=1 to tt do
for k:=1 to 4 do
if (d[j]=0)and(d[s[j][k]]=0)
then begin
t:=i+1;
if k=tr[c[t]]
then temp:=opt[i][j]
else temp:=opt[i][j]+1;
if temp<opt[t][s[j][k]]
then opt[t][s[j][k]]:=temp;
end;
ans:=oo;
for j:=1 to tt do
if opt[m][j]<ans
then ans:=opt[m][j];
write('Case ',ca,': ');
注意到现在的这个图和AC自动机的本质不同
首先每个节点都有所有的后继了 而且每个后继状态都是确定的
确定性和有限性 成为这种图和AC自动机的根本区别
这是AC自动机的一种优化
我们称这种图为 有限状态自动机(DFA)
具有这些性质 我们可以实现更为强大的功能
下一篇文章将会探讨有限状态自动机的具体优势
Pku1625 是Pku3961的类似题 只不过改成了统计种数
方程和更新过程稍微变变即可
注意需要高精度
代码在文章最后
Bob Han原创 转载请注明出处http://www.cnblogs.com/Booble/
代码:
const maxn=2000;
oo=maxlongint;
var tr:array['A'..'Z']of longint;
root,tt,n,m,h,t,i,j,k,temp,p,ans,ca:longint;
opt:array[0..maxn,1..maxn]of longint;
s:array[1..maxn,1..4]of longint;
d,q,f:array[1..maxn]of longint;
c:array[1..maxn]of char;
ch:char;
procedure allot(var x:longint);
var i:longint;
begin
inc(tt); x:=tt;
d[x]:=0; f[x]:=0;
for i:=1 to 4 do
s[x][i]:=0;
end;
begin
assign(input,'RDNA.in'); reset(input);
assign(output,'RDNA1.out'); rewrite(output);
tr['A']:=1; tr['T']:=2;
tr['G']:=3; tr['C']:=4;
ca:=0;
readln(n);
while n<>0 do
begin
inc(ca);
tt:=0;
allot(root);
for i:=1 to n do
begin
p:=root;
while not eoln do
begin
read(ch);
temp:=tr[ch];
if s[p][temp]=0
then allot(s[p][temp]);
p:=s[p][temp];
end;
d[p]:=1;
readln;
end;
h:=1; t:=0;
f[root]:=root;
for i:=1 to 4 do
if s[root][i]<>0
then begin
inc(t);
q[t]:=s[root][i];
f[q[t]]:=root;
end
else s[root][i]:=root;
while h<=t do
begin
for i:=1 to 4 do
if s[q[h]][i]<>0
then begin
inc(t);
q[t]:=s[q[h]][i];
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then f[q[t]]:=root
else begin
if d[s[p][i]]=1
then d[q[t]]:=1;
f[q[t]]:=s[p][i];
end;
end
else begin
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then s[q[h]][i]:=root
else s[q[h]][i]:=s[p][i];
end;
inc(h);
end;
m:=0;
while not eoln do
begin
inc(m);
read(c[m]);
end;
readln;
fillchar(opt,sizeof(opt),$3F);
opt[0][1]:=0;
for i:=0 to m-1 do
for j:=1 to tt do
for k:=1 to 4 do
if (d[j]=0)and(d[s[j][k]]=0)
then begin
t:=i+1;
if k=tr[c[t]]
then temp:=opt[i][j]
else temp:=opt[i][j]+1;
if temp<opt[t][s[j][k]]
then opt[t][s[j][k]]:=temp;
end;
ans:=oo;
for j:=1 to tt do
if opt[m][j]<ans
then ans:=opt[m][j];
write('Case ',ca,': ');
if ans>maxn
then writeln(-1)
else writeln(ans);
readln(n);
end;
close(input); close(output);
end.
const maxn=1000;
oo=$3F3F3F3F;
var tr:array['A'..'Z']of longint;
opt:array[0..maxn,1..maxn]of longint;
s:array[1..maxn,1..4]of longint;
f,q:array[1..maxn]of longint;
d:array[1..maxn]of boolean;
c:array[1..maxn]of char;
root,p,n,m,i,j,k,h,t,tt,ans,temp,ca:longint;
ch:char;
procedure allot(var x:longint);
var i:longint;
begin
inc(tt); x:=tt;
d[x]:=false; f[x]:=0;
for i:=1 to 4 do
s[x][i]:=0;
end;
begin
assign(input,'RDNA.in'); reset(input);
assign(output,'RDNA2.out'); rewrite(output);
tr['A']:=1; tr['C']:=2;
tr['G']:=3; tr['T']:=4;
readln(n);
while n<>0 do
begin
tt:=0;
inc(ca);
allot(root);
for i:=1 to n do
begin
p:=root;
while not eoln do
begin
read(ch);
k:=tr[ch];
if s[p][k]=0
then allot(s[p][k]);
p:=s[p][k];
end;
d[p]:=true;
readln;
end;
h:=1; t:=0;
f[root]:=root;
for i:=1 to 4 do
if s[root][i]<>0
then begin
inc(t);
q[t]:=s[root][i];
f[q[t]]:=root;
end;
while h<=t do
begin
for i:=1 to 4 do
if s[q[h]][i]<>0
then begin
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
inc(t); q[t]:=s[q[h]][i];
if s[p][i]=0
then f[q[t]]:=root
else f[q[t]]:=s[p][i];
if d[f[q[t]]]
then d[q[t]]:=true;
end;
inc(h);
end;
m:=0;
while not eoln do
begin
inc(m);
read(c[m]);
end;
readln;
fillchar(opt,sizeof(opt),$3F);
opt[0][root]:=0;
for i:=0 to m-1 do
for j:=1 to tt do
for k:=1 to 4 do
if not d[j]
then begin
p:=j;
while (p<>root)and(s[p][k]=0) do
p:=f[p];
if s[p][k]=0
then p:=root
else p:=s[p][k];
if d[p] then continue;
t:=i+1;
h:=opt[i][j]+1;
if k=tr[c[t]]
then if opt[t][p]>opt[i][j]
then opt[t][p]:=opt[i][j]
else
else if opt[t][p]>h
then opt[t][p]:=h;
end;
ans:=oo;
for i:=1 to tt do
if ans>opt[m][i]
then ans:=opt[m][i];
write('Case ',ca,': ');
if ans=oo
then writeln(-1)
else writeln(ans);
readln(n);
end;
close(input); close(output);
end.
const maxm=50;
maxk=50;
maxn=101;
maxl=16;
base=1000000;
maxc=char(255);
var tr:array['!'..maxc]of longint;
n,m,h,t,tt,root,p,i,j,k:longint;
opt:array[0..maxm,1..maxn,1..maxl]of longint;
l:array[0..maxm,1..maxn]of longint;
temp:array[1..maxl]of longint;
s:array[1..maxn,1..maxk]of longint;
f,q:array[1..maxn]of longint;
d:array[1..maxn]of boolean;
ch:char;
procedure allot(var x:longint);
var i:longint;
begin
inc(tt); x:=tt;
d[x]:=false; f[x]:=0;
for i:=1 to n do
s[x][i]:=0;
end;
procedure plus(x,y,a,b:longint);
var i,m:longint;
begin
fillchar(temp,sizeof(temp),0);
if l[x][y]<l[a][b]
then m:=l[a][b]
else m:=l[x][y];
for i:=1 to m do
begin
temp[i]:=temp[i]+opt[x][y][i]+opt[a][b][i];
temp[i+1]:=temp[i+1]+temp[i] div base;
temp[i]:=temp[i] mod base;
end;
if temp[m+1]=0
then l[x][y]:=m
else l[x][y]:=m+1;
for i:=1 to l[x][y] do
opt[x][y][i]:=temp[i];
end;
begin
assign(input,'Censor.in'); reset(input);
assign(output,'Censor.out'); rewrite(output);
readln(n,m,t);
for i:=1 to n do
begin
read(ch);
tr[ch]:=i;
end;
readln;
tt:=0;
allot(root);
for i:=1 to t do
begin
p:=root;
while not eoln do
begin
read(ch);
k:=tr[ch];
if k=0
then begin
while true do;
end;
if s[p][k]=0
then allot(s[p][k]);
p:=s[p][k];
end;
d[p]:=true;
readln;
end;
h:=1; t:=0;
f[root]:=root;
for i:=1 to n do
if s[root][i]<>0
then begin
inc(t);
q[t]:=s[root][i];
f[q[t]]:=root;
end;
while h<=t do
begin
for i:=1 to n do
if s[q[h]][i]<>0
then begin
inc(t);
q[t]:=s[q[h]][i];
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then f[q[t]]:=root
else f[q[t]]:=s[p][i];
if d[f[q[t]]]
then d[q[t]]:=true;
end;
inc(h);
end;
l[0][root]:=1;
opt[0][root][1]:=1;
for i:=0 to m-1 do
for j:=1 to tt do
for k:=1 to n do
if not d[j]
then begin
p:=j;
while (p<>root)and(s[p][k]=0) do
p:=f[p];
if s[p][k]=0
then p:=root
else p:=s[p][k];
if d[p] then continue;
plus(i+1,p,i,j);
end;
opt[0][1][1]:=0;
for i:=1 to tt do
plus(0,1,m,i);
t:=l[0][1];
write(opt[0][1][t]);
dec(t);
for i:=t downto 1 do
begin
if opt[0][1][i]<100000 then write(0);
if opt[0][1][i]<10000 then write(0);
if opt[0][1][i]<1000 then write(0);
if opt[0][1][i]<100 then write(0);
if opt[0][1][i]<10 then write(0);
write(opt[0][1][i]);
end;
writeln;
close(input); close(output);
end.
f[root]:=root;
for i:=1 to 4 do
if s[root][i]<>0
then begin
inc(t);
q[t]:=s[root][i];
f[q[t]]:=root;
end
else s[root][i]:=root;
while h<=t do
begin
for i:=1 to 4 do
if s[q[h]][i]<>0
then begin
inc(t);
q[t]:=s[q[h]][i];
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then f[q[t]]:=root
else begin
if d[s[p][i]]=1
then d[q[t]]:=1;
f[q[t]]:=s[p][i];
end;
end
else begin
p:=f[q[h]];
while (p<>root)and(s[p][i]=0) do
p:=f[p];
if s[p][i]=0
then s[q[h]][i]:=root
else s[q[h]][i]:=s[p][i];
end;
inc(h);
end;
posted on 2010-12-07 20:53 Master_Chivu 阅读(2700) 评论(2) 编辑 收藏 举报