AC自動機算法概述及習題

AC自動機習題

1. Censored!

題意:nn 個基本字符組成一個長度爲 mm 的字符串,要求字符串中不能出現給定的 pp 個非法串中任何一個,輸出方案總數。(1n,m50,0p10)(1\leq n,m\leq 50,0\leq p\leq 10)

思路: 數據範圍比較小,因此不難往 dpdp 上進行思考。又因爲有多個非法串,考慮在 ACAC自動機建的整個 TrieTrie 圖上進行 dpdp

我們定義狀態爲 f[i][j]f[i][j],表示長度爲 ii,最後一個字符在 ACAC自動機的第 jj 個節點上,則枚舉 jj 的所有子節點,設 now=next[j][k]now=next[j][k],即 nownowjj 的第 kk 個子節點,則 f[i][now]=f[i][now]+f[i1][j]f[i][now]=f[i][now]+f[i-1][j],當且僅當 nownowjj 不爲非法節點。

因此我們繼續定義非法節點,一個點爲非法節點,即該點所代表的字符串中出現了完整的非法串,很明顯一個非法串的末尾節點是非法節點,並且若 fail[now]fail[now] 是非法節點,則 nownow 也爲非法節點,因爲 fail[now]fail[now] 節點所代表的字符串爲 nownow 節點字符串的後綴。

除此之外,此題還有兩個坑點。

  1. 沒有取模,因此需要大整數。
  2. 字符的 ASCIIASCII 碼範圍在 128128-128~128 之間,rere 了一小時…

總結:ACAC自動機上進行 dpdp,就是以 ACAC自動機上的節點作爲狀態進行轉移,本質上與普通 dpdp 沒有差別。

代碼:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>
#define rep(i,a,b) for(int i = a; i <= b; i++)
typedef long long ll;
using namespace std;

char buf[60],base = 0;
int n,m,p,mp[301];

struct Trie{
	int next[2510][60],fail[2510],end[2510]; //fail[i]是指從root到節點i這一段字母的最長後綴節點
	int root,L;	//L相當於tot
	int newnode()
	{
		for(int i = 0;i < 51;i++)
			next[L][i] = -1; //將root節點的26個子節點都指向-1
		end[L++] = 0; //每一個子節點初始化都不是單詞結尾
		return L-1;
	}
 	void init()
 	{
 		L = 0;
 		root = newnode(); //此處返回root = 0
 	}
 	void insert(char buf[])
 	{
		int len = strlen(buf);
		int now = root;
		for(int i = 0;i < len;i++)
		{
			int pos = mp[buf[i]-base+150];
			if(next[now][pos] == -1)
				next[now][pos] = newnode(); //不能在原有字典樹中匹配的新字母則新建一個節點
			now = next[now][pos];
 		}
 		end[now] = 1;	//now這個節點是一個單詞的結尾
 	}
	void build()
	{
		queue<int>Q;
		fail[root] = root;
		for(int i = 0;i < 51;i++)
		{
			if(next[root][i] == -1)
				next[root][i] = root; //將root的未被訪問的子節點指回root
			else
			{
				fail[next[root][i]] = root; //root子節點的失配指針指向root
				Q.push(next[root][i]); //隊列中加入新節點
			}
		}
		while( !Q.empty() )
		{
			int now = Q.front();
			if(end[fail[now]]) end[now] = 1; //判斷該節點是否非法
			Q.pop();
			for(int i = 0;i < 51;i++)
				if(next[now][i] == -1)
					next[now][i] = next[fail[now]][i]; //now的第i個節點未被訪問,則將now的第i個節點指向now的fail節點的第i個節點
				else
				{
					fail[next[now][i]]=next[fail[now]][i];
					Q.push(next[now][i]);
				}
		}
	}
}ac;

struct BigInteger{
    ll A[50];
    enum{MOD = 1000000};
    BigInteger(){memset(A, 0, sizeof(A)); A[0]=1;}
    void set(ll x){memset(A, 0, sizeof(A)); A[0]=1; A[1]=x;}
    void print(){
        printf("%lld", A[A[0]]);
        for (ll i=A[0]-1; i>0; i--){
            if (A[i]==0){printf("000000"); continue;}
            for (ll k=10; k*A[i]<MOD; k*=10ll) printf("0");
            printf("%lld", A[i]);
        }
        printf("\n");
    }
    ll& operator [] (int p) {return A[p];}
    const ll& operator [] (int p) const {return A[p];}
    BigInteger operator + (const BigInteger& B){
        BigInteger C;
        C[0]=max(A[0], B[0]);
        for (ll i=1; i<=C[0]; i++)
            C[i]+=A[i]+B[i], C[i+1]+=C[i]/MOD, C[i]%=MOD;
        if (C[C[0]+1] > 0) C[0]++;
        return C;
    }
    BigInteger operator * (const BigInteger& B){
        BigInteger C;
        C[0]=A[0]+B[0];
        for (ll i=1; i<=A[0]; i++)
            for (ll j=1; j<=B[0]; j++){
                C[i+j-1]+=A[i]*B[j], C[i+j]+=C[i+j-1]/MOD, C[i+j-1]%=MOD;
            }
        if (C[C[0]] == 0) C[0]--;
        return C;
    }
}f[2][2510];

int main()
{
	scanf("%d%d%d",&n,&m,&p);
	scanf("%s",buf);
	rep(i,0,n-1) mp[buf[i]-base+150] = i;
	ac.init();
	rep(i,1,p){
		scanf("%s",buf);
		ac.insert(buf);
	}
	ac.build();
	f[0][0].set(1);
	rep(i,1,m){
		rep(j,0,ac.L-1) f[i%2][j].set(0);
		rep(j,0,ac.L-1)
			rep(k,0,n-1){
				int now = ac.next[j][k];
				if(ac.end[now] == 0 && ac.end[j] == 0) f[i%2][now] = f[i%2][now] + f[(i-1)%2][j];
			}
	}
	BigInteger ans; ans.set(0);
	rep(i,0,ac.L-1) ans = ans+f[m%2][i];
	ans.print();
	return 0;
}
2. 小明系列故事——女友的考驗

題意: 給定 nnmm,表示一共有 nn 個點,每個點都有其所對應的座標,mm 條非法路徑。先要從 11 號點走到 nn 號點,但路徑中不能出現 mm 條非法路徑中任意一條,求最短距離。(1n50,1m100)(1\leq n\leq 50,1\leq m\leq 100)

思路: ACAC自動機上跑最短路,思路比較明顯。ACAC自動機上的每一個節點都代表一個狀態,且不能通過任何非法節點。

需要在ACAC自動機的根節點的所有兒子上都建立新節點,且如果一個節點的 failfail 節點爲非法節點,則該節點也爲非法節點。

建出 TrieTrie 圖後,直接在圖上跑 dijkstradijkstra 最短路即可。

小坑點:座標之差會爆 intint…(找了半小時 bugbug

代碼:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
const int N = 100+10;
typedef double db;
const db inf = 1e15;
using namespace std;

void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}

int n,m,base[100],vis[25100];
db X[N],Y[N],dis[25100];
struct Node{
	db ans; int a,b;
	bool operator < (Node xx) const {
		return ans > xx.ans;
	}
};
priority_queue<Node> q;

struct Trie{
	int next[25100][60],fail[25100],end[25100]; //fail[i]是指從root到節點i這一段字母的最長後綴節點
	int root,L;	//L相當於tot
	int newnode()
	{
		for(int i = 0;i < 51;i++)
			next[L][i] = -1; //將root節點的26個子節點都指向-1
		end[L++] = 0; //每一個子節點初始化都不是單詞結尾
		return L-1;
	}
 	void init()
 	{
 		L = 0;
 		root = newnode(); //此處返回root = 0
 	}
 	void insert(int len)
 	{
		int now = root;
		for(int i = 0;i < len;i++)
		{
			int pos = base[i];
			if(next[now][pos] == -1)
				next[now][pos] = newnode(); //不能在原有字典樹中匹配的新字母則新建一個節點
			now = next[now][pos];
 		}
 		end[now] = 1;	//now這個節點是一個單詞的結尾
 	}
	void build()
	{
		queue<int> Q;
		fail[root] = root;
		for(int i = 0;i < 51;i++)
		{
			if(next[root][i] == -1)
				next[root][i] = root; //將root的未被訪問的子節點指回root
			else
			{
				fail[next[root][i]] = root; //root子節點的失配指針指向root
				Q.push(next[root][i]); //隊列中加入新節點
			}
		}
		while( !Q.empty() )
		{
			int now = Q.front();
			if(end[fail[now]]) end[now] = 1; //判斷該節點是否非法
			Q.pop();
			for(int i = 0;i < 51;i++)
				if(next[now][i] == -1)
					next[now][i] = next[fail[now]][i]; //now的第i個節點未被訪問,則將now的第i個節點指向now的fail節點的第i個節點
				else
				{
					fail[next[now][i]]=next[fail[now]][i];
					Q.push(next[now][i]);
				}
		}
	}
}ac;

db dist(int i,int j){
	//座標之差會爆int...
	db tmp = (X[i]-X[j])*(X[i]-X[j])+(Y[i]-Y[j])*(Y[i]-Y[j]);
	tmp = sqrt(tmp);
	return tmp;
}

void dijkstra(){
	while(q.size()) q.pop();
	rep(i,0,50)
		if(ac.next[ac.root][i] == 0){
			int p = ac.newnode();
			ac.next[ac.root][i] = p;
			rep(j,0,50) ac.next[p][j] = 0;
		}
	rep(i,0,ac.L) dis[i] = inf, vis[i] = 0;
	q.push({0,ac.next[ac.root][1],1}); dis[ac.next[ac.root][1]] = 0;
	db ans = inf;
	while(q.size()){
		int now = q.top().a, id = q.top().b; q.pop();
		if(vis[now]) continue;
		vis[now] = 1;
		if(id == n) ans = min(ans,dis[now]);
		rep(i,id+1,n){
			db tp = dis[now]+dist(id,i);
			int y1 = now, y2 = ac.next[now][i];
			while(y2 == 0){
				y1 = ac.fail[y1];
				y2 = ac.next[y1][i];
			}
			ac.next[now][i] = y2;
			if(!vis[y2] && !ac.end[y2] && dis[y2] > tp){
				dis[y2] = tp;
				q.push({dis[y2],y2,i});
			}
		}
	}
	if(ans == inf) printf("Can not be reached!\n");
	else printf("%.2f\n",ans);
}

int main(){
	while(~scanf("%d%d",&n,&m)){
		if(n == 0 && m == 0) break;
		rep(i,1,n) scanf("%lf%lf",&X[i],&Y[i]);
		ac.init();
		rep(i,1,m){
			int k; scanf("%d",&k);
			rep(j,0,k-1) scanf("%d",&base[j]);
			ac.insert(k);
		}
		ac.build();
		dijkstra();
	}
}	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章