AC自動機習題
1. Censored!
題意:
用 個基本字符組成一個長度爲 的字符串,要求字符串中不能出現給定的 個非法串中任何一個,輸出方案總數。
思路:
數據範圍比較小,因此不難往 上進行思考。又因爲有多個非法串,考慮在 自動機建的整個 圖上進行 。
我們定義狀態爲 ,表示長度爲 ,最後一個字符在 自動機的第 個節點上,則枚舉 的所有子節點,設 ,即 爲 的第 個子節點,則 ,當且僅當 和 不爲非法節點。
因此我們繼續定義非法節點,一個點爲非法節點,即該點所代表的字符串中出現了完整的非法串,很明顯一個非法串的末尾節點是非法節點,並且若 是非法節點,則 也爲非法節點,因爲 節點所代表的字符串爲 節點字符串的後綴。
除此之外,此題還有兩個坑點。
- 沒有取模,因此需要大整數。
- 字符的 碼範圍在 之間, 了一小時…
總結:
在 自動機上進行 ,就是以 自動機上的節點作爲狀態進行轉移,本質上與普通 沒有差別。
代碼:
#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. 小明系列故事——女友的考驗
題意:
給定 和 ,表示一共有 個點,每個點都有其所對應的座標, 條非法路徑。先要從 號點走到 號點,但路徑中不能出現 條非法路徑中任意一條,求最短距離。
思路:
自動機上跑最短路,思路比較明顯。自動機上的每一個節點都代表一個狀態,且不能通過任何非法節點。
需要在自動機的根節點的所有兒子上都建立新節點,且如果一個節點的 節點爲非法節點,則該節點也爲非法節點。
建出 圖後,直接在圖上跑 最短路即可。
小坑點:座標之差會爆 …(找了半小時 )
代碼:
#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();
}
}