P2444 [POI2000] 病毒
題目核心是多模式匹配,所以考慮用對所有模式串建立AC自動機。
我們把自動機上,存在一個模式串作為字首的節點,稱作“危險節點”。
如果無限長的安全程式碼存在的話,匹配過程中Trie圖上一定有節點會經過多次,即存在環;而且經過的所有節點都不是“危險節點”,否則就包含病毒程式碼段了。
所以我們只需要看Trie圖上是否存在不包含危險節點的環,就能得到答案了。
可以用一個dfs實現這一過程。用\(vis\)來表示節點訪問狀態,初始全為\(0\):
- \(vis[u]=0\):\(u\)未被訪問過。
- \(vis[u]=1\):\(u\)正在處理中。
- \(vis[u]=-1\):從\(u\)出發找不到環。
如果只用\(0,1\)而不用\(-1\)來剪枝最佳化,時間複雜度可能由線性退化到指數級,將無法透過hack資料。
bool dfs(int u){
if(vis[u]==1) return 1;
if(vis[u]==-1) return 0;
vis[u]=1;
for(int i=0;i<C;i++)//C=2
if(!en[tr[u][i]]&&dfs(tr[u][i]))
return 1;//en[x]=1表示x是危險節點
vis[u]=-1;
return 0;
}
最後就是如何判斷某個節點是不是危險節點,這個可以在get_fail()
中處理出來。如果節點\(u\)是模式串的結尾,或者\(fail[u]\)是危險節點,那麼\(u\)就是危險節點。
時間複雜度\(O(\sum|s|\times |\Sigma|)\)。
點選檢視程式碼
#include<bits/stdc++.h>
#define N 30010//節點數(模式串總長)
#define C 2//字符集大小
using namespace std;
int n,tr[N][C],fail[N],cnt;
int q[N],head,tail,vis[N];
bool en[N];
string s;
void ins(string s){
int p=0;
for(char i:s){
int c=i-'0';
if(!tr[p][c]) tr[p][c]=++cnt;
p=tr[p][c];
}
en[p]=1;
}
void get_fail(){
head=0,tail=-1;
for(int i=0;i<C;i++) if(tr[0][i]) q[++tail]=tr[0][i];
while(head<=tail){
int u=q[head++];
for(int i=0;i<C;i++){
int& v=tr[u][i];
if(v) fail[v]=tr[fail[u]][i],q[++tail]=v,
en[v]|=en[fail[v]];
else v=tr[fail[u]][i];
}
}
}
bool dfs(int u){
if(vis[u]==1) return 1;
if(vis[u]==-1) return 0;
vis[u]=1;
for(int i=0;i<C;i++)
if(!en[tr[u][i]]&&dfs(tr[u][i]))
return 1;
vis[u]=-1;
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>s;
ins(s);
}
get_fail();
if(dfs(0)) cout<<"TAK\n";
else cout<<"NIE\n";
return 0;
}