[題解]P2444 [POI2000] 病毒

Sinktank發表於2024-08-21

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;
}

相關文章