【pku3691+pku2778】ac自動機 動態規劃

來源:互聯網
上載者:User

再講題目之前,先講講ac自動機吧

看到ac這麼吉利的東西,學會這什麼ac自動機的慾望便來了

ac自動機全名Aho-Corasick automation,挺長的,但經驗告訴我們,名字越長往往編出來的代碼越短,所以不要被這麼虎的名字給唬住了

好了,進入正題

ac自動機就是用來進行多串模式比對的一種演算法(不算是一種什麼神奇的資料結構吧),它差不多就等於trie+kmp

這個演算法的思想非常簡單,就在字母樹上弄個next指標(相當於kmp中的next函數),然後對於每一篇文章我們就可以根據這棵字母樹來O(len)的得到那些單詞出現過(貌似要統計出現個數有難度,這恐怕就是這個演算法的局限性吧)

這個演算法的關鍵就是計算next[i],方法如下:

  對於每個結點i,若它就是root,則next[i]=0,否則

  在i的父親的next找父鏈(就是一路j:=next[j]上去,顯然不能算進i的父親)上找到第一個點的兒子中有i帶的字元的點,

next[i]=這個兒子,如果找不到,則next[i]=root

就這麼簡單,具體實現起來的話廣搜就可以了

然後,給了一段文章,我們要拿他與給定的單詞進行匹配,就用剛才做好的字母樹+next指標直接做就可以了(像kmp那樣)

特別要注意,每匹配完一位,就要在next鏈上往回找有沒有單詞恰好在這一位完成匹配,而已經出現了的單詞,我們可以直接把它的標記去掉就可以了(下面的代碼中帶!!的地方需注意)

實現起來有個小的改動可以讓代碼更簡潔,那就是把字母樹中的c[0,i]賦為1,那麼next鏈到底時,再直接用c[0,i]就可以回到root了(我的root=1)就不用每次(k<>root)and ...了,可以顯得漂亮一點

下面給出我的ac_auto的模板

program syj; {Aho-Corasick automation-used to O(n) perfix}<br />const<br /> maxt=500000+5;<br />type<br /> stype=ansistring;<br />var i,j,k,n,m,t,ans,task:longint;<br /> cc:char;<br /> st,a:stype;<br /> word,next,q:array[0..maxt]of longint;<br /> c:array[0..maxt,'a'..'z']of longint;<br />procedure bfs;<br />var st,ed,i,j:longint;<br /> cc:char;<br />begin<br /> st:=0;ed:=1;q[1]:=1;<br /> while st<ed do begin<br /> inc(st);i:=q[st];<br /> if i=3 then<br /> i:=i;<br /> for cc:='a' to 'z' do if c[i,cc]>0 then begin<br /> inc(ed);q[ed]:=c[i,cc];j:=next[i];<br /> while (j>0)and(c[j,cc]=0) do j:=next[j];<br /> next[c[i,cc]]:=c[j,cc];<br /> end;<br /> end;<br />end;<br />procedure ins(k:longint);<br />var i,j:longint;<br />begin<br /> i:=1;<br /> for j:=1 to length(st) do<br /> if c[i,st[j]]>0 then i:=c[i,st[j]]<br /> else begin<br /> inc(t);c[i,st[j]]:=t;i:=t;<br /> end;<br /> word[i]:=1;<br />end;<br />begin<br /> assign(input,'input.txt');reset(input);<br /> assign(output,'output.txt');rewrite(output);<br /> for cc:='a' to 'z' do c[0,cc]:=1;<br /> readln(n,m);t:=1;<br /> readln(a);<br /> for i:=1 to n do begin<br /> readln(st);ins(i);<br /> end;<br /> bfs;j:=1;<br /> for i:=1 to m do begin<br /> cc:=a[i];<br /> while c[j,cc]=0 do j:=next[j];<br /> j:=c[j,cc];k:=j;<br /> while k>1 do begin //!!<br /> ans:=ans+word[k];word[k]:=0;<br /> k:=next[k];<br /> end;<br /> end;<br /> writeln(ans);<br /> close(input);<br /> close(output);<br />end.<br />  

第一次寫這個調了幾個小時才過(就是!!那裡一直出問題),後來才發現每個用ac自動機的題目都要注意這裡!

 

講完了演算法,就來看看題目吧

pku3691

大意:給若干個單詞,一段文章,要求這段文章至少改動幾個才能不含任何一個單詞,字母只有ACGT

分析:

   這個題dp的方向很明顯,只是狀態的設計有點難度(在學會ac自動機之前,咳咳,我應該是做不出的)

   那麼我就先入為主了,考慮怎麼在自動機的那棵字母樹上做文章

   可以想到如果匹配完了原串的前i位,當前到了字母樹的j號結點,只要有了這個i和j就能夠無後效地表示出整個匹配的狀態了,那麼演算法就出來了

演算法:

   記:

   f[i,j]表示匹配完了原串的前i位,當前到了字母樹的j號結點的最少要改動的個數,轉移就枚舉第i+1位改成什麼或者不改,用上述演算法推到下一個狀態就可以了,注意不能轉移到的結點,要求不能成功匹配任意一個單詞

   邊界f[0,1]=0,答案min{f[len,i]}(word[i]=false)

需要注意的是每要轉移到一個結點,就得在next鏈上掃一遍,看有沒有word[i]為true,有就不能轉移了(上面提到過的),還有麻煩的輸出格式和ansistring(我wa了3次格式,1次ansistring),多組資料還得記得清空數組

代碼:

program syj; {dp + ac_automation}<br />const<br /> maxl=1000+5;<br /> maxt=20*50+5;<br /> oo=maxlongint shr 1;<br />var n,i,j,k,kk,l,len,t,ans,task,r:longint;<br /> u:array['A'..'Z']of longint;<br /> st:ansistring;<br /> ok:boolean;<br /> word:array[0..maxt]of boolean;<br /> next,q:array[0..maxt]of longint;<br /> f:array[0..maxl,0..maxt]of longint;<br /> c:array[0..maxt,1..4]of longint;<br />procedure new;<br />var i,j:longint;<br />begin<br /> j:=1;<br /> for i:=1 to length(st) do<br /> if c[j,u[st[i]]]>0 then j:=c[j,u[st[i]]]<br /> else begin<br /> inc(t);c[j,u[st[i]]]:=t;j:=t;<br /> end;<br /> word[j]:=true;<br />end;<br />procedure bfs;<br />var st,ed,i,j,k:longint;<br />begin<br /> st:=0;ed:=1;q[1]:=1;<br /> while st<ed do begin<br /> inc(st);i:=q[st];<br /> for j:=1 to 4 do if c[i,j]>0 then begin<br /> inc(ed);q[ed]:=c[i,j];k:=next[i];<br /> while c[k,j]=0 do k:=next[k];<br /> next[c[i,j]]:=c[k,j];<br /> end;<br /> end;<br />end;<br />begin<br /> assign(input,'pku3691.in');reset(input);<br /> assign(output,'pku3691.out');rewrite(output);<br /> u['A']:=1;u['C']:=2;u['G']:=3;u['T']:=4;<br /> readln(n); task:=0;<br /> while n>0 do begin<br /> inc(task);<br /> fillchar(next,sizeof(next),0);<br /> fillchar(word,sizeof(word),0);<br /> fillchar(c,sizeof(c),0);<br /> t:=1;<br /> for i:=1 to n do begin<br /> readln(st);new;<br /> end;<br /> c[0,1]:=1;c[0,2]:=1;c[0,3]:=1;c[0,4]:=1;<br /> bfs;<br /> readln(st);len:=length(st);<br /> filldword(f,sizeof(f) div 4,oo);<br /> f[0,1]:=0;<br /> for i:=0 to len-1 do<br /> for j:=1 to t do if f[i,j]<oo then begin<br /> kk:=u[st[i+1]];<br /> for k:=1 to 4 do begin<br /> l:=j;<br /> while c[l,k]=0 do l:=next[l];<br /> l:=c[l,k];ok:=word[l];r:=l;<br /> while r>1 do begin<br /> r:=next[r];ok:=ok or word[r];<br /> if ok then break;<br /> end;<br /> if not ok and (f[i,j]+ord(k<>kk)<f[i+1,l]) then<br /> f[i+1,l]:=f[i,j]+ord(k<>kk);<br /> end;<br /> end;<br /> ans:=oo;<br /> for i:=1 to t do if not word[i] and (f[len,i]<ans) then<br /> ans:=f[len,i];<br /> if ans=oo then ans:=-1;<br /> writeln('Case ',task,': ',ans);<br /> readln(n);<br /> end;<br /> close(input);<br /> close(output);<br />end.<br />

 

pku2778

大意:給你若干個單詞,求長度為n的dna串(只含'A''C''G''T')中不含任何一個單詞的串的個數(mod 100000)(n<=200000000)

分析及演算法:

   同2778類似的,dp的解題方向不會錯

   這個題目中,n這個天文數字般的範圍似乎在告訴我們這題用是矩陣加速的dp題,那麼我就只講轉移矩陣的構造了,關於矩陣最佳化你可以看網上其他的資料

   對於字母樹中的結點i,枚舉下一個字元是什麼,到了結點j,只要沒有一個單詞匹配成功(還是那個要注意的地方,要在next鏈上掃一遍)就可以轉移,inc(g[i,j])即可

   再快速冪,ans=sigma(f[1,i]) mod mo (word[i]=false) (我的root=1)

代碼:

program syj; {dp + ac_automation}<br />const<br /> maxt=100;<br /> mo=100000;<br />type<br /> arr=array[1..maxt,1..maxt]of int64;<br />var m,t,i,j,k,l:longint;<br /> u:array['A'..'Z']of longint;<br /> st:ansistring;<br /> ok:boolean;<br /> n,ans:int64;<br /> word:array[0..maxt]of boolean;<br /> next,q:array[0..maxt]of longint;<br /> f,g:arr;<br /> c:array[0..maxt,1..4]of longint;<br />procedure init;<br />var i,j:longint;<br />begin<br /> readln(st);j:=1;<br /> for i:=1 to length(st) do<br /> if c[j,u[st[i]]]>0 then j:=c[j,u[st[i]]]<br /> else begin<br /> inc(t);c[j,u[st[i]]]:=t;j:=t;<br /> end;<br /> word[j]:=true;<br />end;<br />procedure bfs;<br />var st,ed,i,j,k:longint;<br />begin<br /> st:=0;ed:=1;q[1]:=1;<br /> while st<ed do begin<br /> inc(st);i:=q[st];<br /> for j:=1 to 4 do if c[i,j]>0 then begin<br /> inc(ed);q[ed]:=c[i,j];k:=next[i];<br /> while c[k,j]=0 do k:=next[k];<br /> next[c[i,j]]:=c[k,j];<br /> end;<br /> end;<br />end;<br />procedure cheng(a,b:arr;var c:arr);<br />var i,j,k:longint;<br /> tmp:int64;<br />begin<br /> for i:=1 to t do<br /> for j:=1 to t do begin<br /> tmp:=0;<br /> for k:=1 to t do tmp:=tmp+a[i,k]*b[k,j];<br /> c[i,j]:=tmp mod mo;<br /> end;<br />end;<br />procedure calc(i:longint);<br />begin<br /> if i=1 then f:=g<br /> else begin<br /> calc(i div 2);<br /> cheng(f,f,f);<br /> if odd(i) then cheng(f,g,f);<br /> end;<br />end;<br />begin<br /> assign(input,'pku2778.in');reset(input);<br /> assign(output,'pku2778.out');rewrite(output);<br /> u['A']:=1;u['C']:=2;u['G']:=3;u['T']:=4;<br /> c[0,1]:=1;c[0,2]:=1;c[0,3]:=1;c[0,4]:=1;<br /> readln(m,n);t:=1;<br /> for i:=1 to m do init;<br /> bfs;<br /> for i:=1 to t do<br /> for j:=1 to 4 do begin<br /> k:=i;<br /> while c[k,j]=0 do k:=next[k];<br /> k:=c[k,j];l:=k;ok:=false;<br /> while l>1 do begin<br /> ok:=ok or word[l];<br /> l:=next[l];<br /> end;<br /> if not ok then inc(g[i,k]);<br /> end;<br /> calc(n);<br /> ans:=0;<br /> for i:=1 to t do<br /> if not word[i] then inc(ans,f[1,i]);<br /> writeln(ans mod mo);<br /> close(input);<br /> close(output);<br />end.<br />

這道題寫的相當流暢,一次ac,感覺反正是越來越好

 

好了,至此,ac自動機應該差不多就這樣了吧,發現自動機主要是用來最佳化有關多串模式比對的dp中的狀態表示,以後碰到題目得往這方面想想了。希望ac自動機能給我們帶來更多ac的喜悅吧!

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.