AC自動機總結
AC自動機簡述
功能
多模板串對單個或多個串的匹配問題
主體思想
原理同\(kmp\) , 在\(trie\)樹上使用變種的\(kmp\)
實現
需要數組 : \(trie[N][26],fail[N]\)
\(fail\)即我們所說的失配函數,\(trie[]\)則略有變更
准確一點得說,\(fail\)函數是不需要知道后繼字母的失配
而\(trie\)樹上的節點經過處理后,就可以直接\(O(1)\)訪問后繼每一種字母的情況的下一個位置,不需要再一次次失配
預處理
AC自動機的預處理與\(kmp\)的預處理只有一點不同
\(kmp\)的 失配數組\(nxt[N]\) , 是在不知道下一位的字母,一次次失配直到與下一位匹配
int j=0;
for(int i=1;i<=n;++i){
while(j && s[i]!=t[j+1]) j=nxt[j];
if(s[i]==t[j+1]) j++;
}
然而AC自動機既然已經基於\(trie\)樹結構,自然可以對於每個下一位字母的情況來匹配,這里我們分類討論
如果已經存在同字母的節點,那么就是下一位節點,而它的\(fail\)就是上一個失配位置\(fail\)的這一個字母的后繼節點
如果不存在,就是上一個失配位置\(fail\)的這一個字母的后繼節點
(?Are You Kidding Me ?)
事實上是,AC自動機上的\(trie\)樹節點,對於不存在的點,我們開出一個虛點作為這個點下一位是這個字母的nxt
而這個過程就可以通過上面分類討論里描述的方式遞推
這樣處理下一位的匹配時,就能夠直接訪問\(trie\)樹上所指向的節點
預處理我們通過廣搜來遞推每個點的\(fail,trie[]\)
struct AC_automation{
static const int SIZE=N*26;
int trie[SIZE][26],cnt,fail[SIZE];
void Build(char s[N][51]){
rep(i,1,n) Insert(s[i]);
static queue <int> que;
rep(i,0,25) if(trie[0][i]) {
que.push(trie[0][i]);
fail[trie[0][i]]=0;
}
while(!que.empty()) {
int u=que.front(); que.pop();
rep(i,0,25) {
int &v=trie[u][i];
if(v) {
fail[v]=trie[fail[u]][i];
que.push(v);
} else v=trie[fail[u]][i];
}
}
}
}AC;
如果你還不清晰,那沒有關系,大不了我們先從背板子做起
關於插敘操作的實現
我們已經知道,每個點的后繼狀態可以直接通過\(trie\)數組訪問
而這里的查詢並不只有這么簡單
事實上,每一個節點對應的不止是這個節點上所對應的的模版串末尾,
因為模版串直接會含有前后綴的包含關系,所以我們這里要通過一次次的強行失配來訪問這個節點對應的所有后綴包含的模版串
寫成代碼就是
void Que(char *s){
int p=0,n=strlen(s+1);
rep(i,1,n) {
p=trie[p][s[i]-'a'];
for(int j=p;j;j=fail[j]) {
;
;//這里該干啥干啥
}
}
}
對於最基礎的單串訪問,我們直接開一個標記記錄這個節點是否被加過答案即可
struct AC_automation{
static const int SIZE=N*26;
int trie[SIZE][26],End[SIZE],cnt,fail[SIZE],vis[SIZE];
int Query(char *s) {
memset(vis,0,sizeof vis);
int Ans=0;
int p=0;
rep(i,0,strlen(s)-1) {
int x=s[i]-'a';
p=trie[p][x];
for(int j=p;j && !vis[j];j=fail[j]) {//注意如果已經訪問就可以break了
Ans+=End[j];
vis[j]=1;
}
}
return Ans;
}
}AC;
int main(){
rep(kase,1,rd()) {
AC.clear();
n=rd();
rep(i,1,n) scanf("%s",s[i]);
AC.Build(s);
scanf("%s",str);
printf("%d\n",AC.Query(str));
}
}
好的現在我們可以愉快得去A掉模板題了
話說我還寫了一種指針的,由於是第一次寫指針,寫的比較粗糙,跑得也不快
int n,m,k;
char s[N][51];
char str[M];
struct AC_automation{
struct Node{
int End,vis;
Node *son[26],*fail;
Node(){
End=0;
rep(i,0,25) son[i]=NULL;
fail=NULL;
vis=0;
}
} rt;
void clear(){ rt=Node(); }
void Insert(char *s){
Node *p=&rt;
rep(i,0,strlen(s)-1) {
int x=s[i]-'a';
if(p->son[x]==NULL) p->son[x]=new Node();
p=p->son[x];
}
p->End++;
}
void Build(char s[N][51]){
rep(i,1,n) Insert(s[i]);
static queue <Node*> que;
rep(i,0,25) if(rt.son[i]!=NULL) {
que.push(rt.son[i]);
rt.son[i]->fail=&rt;
} else rt.son[i]=&rt;
while(!que.empty()) {
Node *u=que.front(); que.pop();
rep(i,0,25) {
if(u->son[i]!=NULL) {
u->son[i]->fail=u->fail->son[i];
que.push(u->son[i]);
} else u->son[i]=u->fail->son[i];
}
}
}
int Query(char *s) {
int Ans=0;
Node *p=&rt;
rep(i,0,strlen(s)-1) {
int x=s[i]-'a';
p=p->son[x];
for(Node *j=p;j!=NULL && !j->vis; j=j->fail) {
Ans+=j->End;
j->vis=1;
}
}
return Ans;
}
}AC;
int main(){
rep(kase,1,rd()) {
AC.clear();
n=rd();
rep(i,1,n) scanf("%s",s[i]);
AC.Build(s);
scanf("%s",str);
printf("%d\n",AC.Query(str));
}
}
//http://acm.hdu.edu.cn/showproblem.php?pid=2222
學完模板的旁友們先不要跑!你們還不會多串匹配呢!
觀察到我們的每個點都有一個所指向的\(fail\)節點,令這個點為父親,我們就能得到一棵有\(fail\)指針構成的樹,暫且稱其為\(fail\)樹吧
所以我們訪問每個節點時,其實就是訪問到了其在\(fail\)樹上所對應的的一段到根的路徑上所對應的點
這個東西我們就可以有請各種神仙解法來維護啦(建議自己think think)
練習
溫馨提示:AC自動機的練習題可能卡內存
POJ - 1204
把要查詢的串都扔進AC自動機,然后暴力check就是了
const int z[10][4]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
int n,m,q;
char s[N][N];
char str[N][N];
int px[N],py[N],dir[N];
namespace AC{
static const int SIZE=1e6+10;
int trie[SIZE][26];
vector <int> End[SIZE];
int fail[SIZE],cnt,vis[SIZE];
int Insert(char *s){
int p=0;
rep(i,0,strlen(s)-1) {
int x=s[i]-'A';
((!trie[p][x])&&(trie[p][x]=++cnt));
p=trie[p][x];
}
//cout<<"Add"<<p<<endl;;
return p;
}
void Build(char s[N][N]){
rep(i,1,q) End[Insert(s[i])].push_back((int)i);
static queue <int> que;
rep(i,0,25) if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty()) {
int u=que.front(); que.pop();
//cout<<u<<endl;
rep(i,0,25) {
int &v=trie[u][i];
if(v) {
fail[v]=trie[fail[u]][i];
que.push(v);
} else v=trie[fail[u]][i];
}
}
}
void dfs(int x,int y,int d,int p) {
//cout<<x<<' '<<y<<' '<<d<<' '<<p<<endl;
p=trie[p][s[x][y]-'A'];
for(int j=p; j && !vis[j]; j=fail[j]) {
vis[j]=1;
rep(k,0,End[j].size()-1) {
int t=End[j][k];
px[t]=x,py[t]=y;
dir[t]=d;
}
}
x+=z[d][0],y+=z[d][1];
if(x>=n||y>=m||x<0||y<0) return;
dfs(x,y,d,p);
}
}
using AC::dfs;
int main(){
n=rd(),m=rd(),q=rd();
rep(i,0,n-1) scanf("%s",s[i]);
rep(i,1,q) scanf("%s",str[i]);
AC::Build(str);
rep(i,0,m-1) dfs(n-1,i,0,0);
rep(i,0,n-1) dfs(i,0,1,0); rep(j,0,m-1) dfs(n-1,j,1,0);
rep(i,0,n-1) dfs(i,0,2,0);
rep(i,0,n-1) dfs(i,0,3,0); rep(j,0,m-1) dfs(0,j,3,0);
rep(i,0,m-1) dfs(0,i,4,0);
rep(i,0,m-1) dfs(0,i,5,0); rep(i,0,n-1) dfs(i,m-1,5,0);
rep(i,0,n-1) dfs(i,m-1,6,0);
rep(i,0,n-1) dfs(i,m-1,7,0); rep(j,0,m-1) dfs(n-1,j,7,0);
rep(i,1,q) {
int l=strlen(str[i])-1;
printf("%d %d %c\n",px[i]-l*z[dir[i]][0],py[i]-l*z[dir[i]][1],'A'+dir[i]);
}
}
ZOJ - 3228
我寫的比較奇怪
先把模板串都丟進去
然后跑插敘
對於允許重疊的,我們直接對\(fail\)樹上一段路徑的節點的答案++
否則我們分串的長度討論,對於每種長度的串處理一個答案\(dp[i][6]\)
int n;
char s[N][10],str[N];
int kind[N],End[N];
const int SIZE=N*6;
int dp[SIZE][7],pre[SIZE][7];
namespace AC{
int trie[SIZE][26];
int fail[SIZE],cnt;
void clear(){
cnt=0;
memset(trie,0,sizeof trie);
}
int Insert(char *s){
int p=0;
rep(i,0,strlen(s)-1) {
int x=s[i]-'a';
((!trie[p][x])&&(trie[p][x]=++cnt));
p=trie[p][x];
}
return p;
}
void Build(){
static queue <int> que;
rep(i,0,25) if(trie[0][i]) {
que.push(trie[0][i]);
fail[trie[0][i]]=0;
}
while(!que.empty()) {
int u=que.front(); que.pop();
rep(i,0,25) {
int &v=trie[u][i];
if(v) {
fail[v]=trie[fail[u]][i];
que.push(v);
} else v=trie[fail[u]][i];
}
}
}
void Que(char *s) {
memset(dp,0,sizeof dp);
memset(pre,-63,sizeof pre);
int p=0;
rep(i,0,strlen(s)-1) {
p=trie[p][s[i]-'a'];
for(reg int j=p; j; j=fail[j]) {
dp[j][0]++;
rep(k,1,6) {
if(i-pre[j][k]>=k) {
dp[j][k]++;
pre[j][k]=i;
}
}
}
}
}
}
int main(){
int kase=0;
while(~scanf("%s",str)) {
printf("Case %d\n",++kase);
AC::clear();
n=rd();
rep(i,1,n) {
kind[i]=rd();
scanf("%s",s[i]);
if(kind[i]) kind[i]=strlen(s[i]);
End[i]=AC::Insert(s[i]);
}
AC::Build();
AC::Que(str);
rep(i,1,n) printf("%d\n",dp[End[i]][kind[i]]);
puts("");
}
}
HDU - 2457
把AC自動機上的狀態存進dp狀態里即可
const int N=1e3+10,M=1e7+100,INF=1e9+10;
const int z[10][4]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
void chk(int &a,int b){ ((a>b)&&(a=b)); }
int n;
char s[N];
int ch[N];
//AC automation
const int SIZE=N;
int trie[SIZE][4];
int End[SIZE];
int fail[SIZE],cnt;
void clear(){
cnt=0;
memset(End,0,sizeof End);
memset(trie,0,sizeof trie);
memset(fail,0,sizeof fail);
}
void Insert(char *s){
int p=0;
rep(i,0,strlen(s)-1) {
int x=ch[(int)s[i]];
if(!trie[p][x]) trie[p][x]=++cnt;
p=trie[p][x];
}
End[p]=1;
}
void Build() {
static queue <int> que;
rep(i,0,3) if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty()) {
int u=que.front(); que.pop();
End[u]|=End[fail[u]];
rep(i,0,3) {
int &v=trie[u][i];
if(v) {
que.push(v);
fail[v]=trie[fail[u]][i];
} else v=trie[fail[u]][i];
}
}
}
int dp[N][N];
int kase;
int main(){
ch[(int)'A']=0,ch[(int)'T']=1,ch[(int)'C']=2,ch[(int)'G']=3;
while(~scanf("%d",&n) && n) {
clear();
rep(i,1,n) scanf("%s",s),Insert(s);
Build();
scanf("%s",s+1);
int m=strlen(s+1);
memset(dp,63,sizeof dp);
dp[0][0]=0;
rep(i,1,m) {
rep(j,0,cnt) if(!End[j] && dp[i-1][j]<INF) {
int t=ch[(int)s[i]];
rep(k,0,3) chk(dp[i][trie[j][k]],dp[i-1][j]+(k!=t));
}
}
int ans=INF;
rep(j,0,cnt) if(!End[j]) chk(ans,dp[m][j]);
printf("Case %d: %d\n",++kase,ans<INF?ans:-1);
}
}
POJ - 2778
再套一個矩陣就好了
int n,m;
int a[N];
char s[N];
int val[N];
//AC automation
const int SIZE=101;
int trie[SIZE][4];
int End[SIZE];
int fail[SIZE],cnt;
int ch[N];
int Insert(char *s){
int p=0;
int l=0;
while(s[l]!='\0') l++;
rep(i,0,l-1) {
int x=ch[(int)s[i]];
//cout<<"insert"<<s<<' '<<i<<' '<<x<<endl;
if(!trie[p][x]) trie[p][x]=++cnt;
p=trie[p][x];
}
//cout<<p<<endl;
return p;
}
void Build() {
static queue <int> que;
rep(i,0,3) if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty()) {
int u=que.front(); que.pop();
End[u]|=End[fail[u]];
rep(i,0,3) {
int &v=trie[u][i];
if(v) {
que.push(v);
fail[v]=trie[fail[u]][i];
} else v=trie[fail[u]][i];
}
}
}
struct Mat{
int a[SIZE][SIZE];
void init(){ memset(a,0,sizeof a); }
void Get1(){ rep(i,0,cnt) a[i][i]=1; }
Mat operator * (const Mat x) const{
Mat res; res.init();
rep(i,0,cnt) rep(j,0,cnt) rep(o,0,cnt) res.a[i][o]=(res.a[i][o]+1ll*a[i][j]*x.a[j][o])%P;
return res;
}
}x,res;
int f[1][SIZE],ans[1][SIZE];
int main(){
ch[(int)'A']=0,ch[(int)'T']=1,ch[(int)'C']=2,ch[(int)'G']=3;
m=rd(),n=rd();
rep(i,1,m) {
scanf("%s",s);
End[Insert(s)]=1;
}
Build();
//puts("!");
f[0][0]=1;
rep(i,0,cnt) if(!End[i]) {
rep(j,0,3) {
int nxt=trie[i][j];
if(End[nxt]) continue;
x.a[i][nxt]++;
}
}
res.Get1();
//puts("!");
while(n) {
if(n&1) res=res*x;
x=x*x;
n>>=1;
}
rep(i,0,0) rep(j,0,cnt) rep(o,0,cnt) ans[i][o]=(ans[i][o]+1ll*f[i][j]*res.a[j][o])%P;
int Ans=0;
rep(i,0,cnt) (Ans+=ans[0][i])%=P;
printf("%d\n",Ans);
}
HYSBZ - 3172
嗯,題目中的文章是由所有單詞拼出來的,但是單詞直接互相獨立
這道題我們才第一次用到一點\(fail\)樹
首先單詞都丟進AC自動機,然后一個個跑匹配
每一次更新\(fail\)樹上一段路徑前綴的節點即可
事實上就是在哪里放一個1,按次向根累加就能得到答案
int n;
int l;
string s[N];
int End[N];
const int SIZE=N;
int trie[N][26],fail[N],cnt;
int Insert(string &s){
int p=0;
rep(i,0,s.size()-1) {
int x=s[i]-'a';
if(!trie[p][x]) trie[p][x]=++cnt;
p=trie[p][x];
}
return p;
}
int line[SIZE],Ans[SIZE],lc;
void Build(){
static queue <int> que;
rep(i,0,25) if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty()){
int u=que.front(); que.pop();
line[++lc]=u;
rep(i,0,25) {
int &v=trie[u][i];
if(v) {
que.push(v);
fail[v]=trie[fail[u]][i];
} else v=trie[fail[u]][i];
}
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
rep(i,1,n) {
cin>>s[i];
End[i]=Insert(s[i]);
}
Build();
rep(i,1,n){
int p=0;
rep(j,0,s[i].size()-1) {
p=trie[p][s[i][j]-'a'];
Ans[p]++;
}
}
drep(i,lc,1) Ans[fail[line[i]]]+=Ans[line[i]];//利用廣搜序累和
rep(i,1,n) printf("%d\n",Ans[End[i]]);
}
HDU - 5069
求后綴與前綴的最大匹配長度
這題我寫的很暴力
首先對於前一個字符串,直接匹配到末尾,其實接下來就是求\(fail\)樹上的這段前綴上的每一個點與后一個串在\(trie\)樹上的位置的最長公共前綴長度
我的做法是:將\(trie\)樹樹剖,依次訪問\(fail\)樹上的每一個節點回答詢問
訪問\(fail\)樹上的節點前綴時就可以直接通過每經過一個點就++
對於這一段前綴的一個詢問串x,令其在\(trie\)樹上的末尾節點為y,我們就是找到所有++的節點最深的在\(y\)對應的\(trie\)樹前綴上的位置,這里我直接樹剖加二分實現了
int n,m;
string s[N];
const int SIZE=N;
int End[N];
int trie[N][26],fail[N],cnt;
void clear(){
memset(trie,0,sizeof trie);
cnt=0;
}
int Insert(string &s){
int p=0;
rep(i,0,s.size()-1) {
int x=s[i]-'A';
if(!trie[p][x]) trie[p][x]=++cnt;
p=trie[p][x];
}
return p;
}
struct Graph{
struct Edge{
int to,nxt;
}e[SIZE];
int head[N],ecnt;
void AddEdge(int u,int v){
//cout<<"Edge ont the fail "<<u<<' '<<v<<endl;
e[++ecnt].to=v;e[ecnt].nxt=head[u];
head[u]=ecnt;
}
void clear(){
memset(head,0,sizeof head);
ecnt=0;
}
}G;
int dep[N];
int sz[N],son[N],top[N],fa[N];
void dfs1(int u){
//cout<<"dfs on the trie"<<u<<endl;
sz[u]=1,son[u]=-1;
rep(i,0,25) {
int v=trie[u][i];
if(!v) continue;
dep[v]=dep[u]+1;
fa[v]=u;
//cout<<"Edge on the trie "<<u<<' '<<v<<endl;
dfs1(v);
sz[u]+=sz[v];
if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
}
}
int L[N],R[N],dfn,id[N];
void dfs2(int u,int t){
//cout<<"dfs on the trie"<<u<<endl;
top[u]=t;
id[L[u]=++dfn]=u;
if(~son[u]) dfs2(son[u],t);
rep(i,0,25) {
int v=trie[u][i];
if(!v||v==son[u]) continue;
dfs2(v,v);
}
R[u]=dfn;
}
void Build(){
static queue <int> que;
dfn=0;G.clear();
fa[0]=-1,dfs1(0);dfs2(0,0);
rep(i,0,25) if(trie[0][i]) {
que.push(trie[0][i]);
fail[trie[0][i]]=0;
}
while(!que.empty()){
int u=que.front(); que.pop();
//cout<<"#"<<u<<endl;
G.AddEdge(fail[u],u);
rep(i,0,25) {
int &v=trie[u][i];
if(v) {
//cout<<v<<endl;
que.push(v);
fail[v]=trie[fail[u]][i];
} else v=trie[fail[u]][i];
}
}
}
vector <pair<int,int> > Q[N];
int sum[N];
void Add(int p,int x){
//cout<<"Add "<<p<<' '<<x<<' '<<endl;
while(p<=dfn) sum[p]+=x,p+=p&-p;
}
int Que(int p){
int res=0;
while(p) res+=sum[p],p-=p&-p;
return res;
}
int Ans[N];
void dfs_getans(int u){
Add(L[u],1);
rep(i,0,Q[u].size()-1) {
int x=Q[u][i].first,qid=Q[u][i].second;
while(~x) {
int t=Que(L[x])-Que(L[top[x]]-1);
if(t) {//找到一個++的節點
int p=0;
t+=Que(L[top[x]]-1);
drep(j,16,0) if( (p+(1<<j)<L[x]) && sum[p+(1<<j)]<t) t-=sum[p+=(1<<j)];
p++;
Ans[qid]=dep[id[p]];
break;//二分這個節點的位置
}
x=fa[top[x]];
}
}
vector <pair<int,int> > tmp;
swap(tmp,Q[u]);
for(int i=G.head[u];i;i=G.e[i].nxt) {
int v=G.e[i].to;
dfs_getans(v);
}
Add(L[u],-1);
}
bool ed;
int main(){
ios::sync_with_stdio(false);
while(cin>>n>>m) {
clear();
rep(i,1,n) {
cin>>s[i];
End[i]=Insert(s[i]);
}
Build();
rep(i,1,m) {
int x=rd(),y=rd();
x=End[x],y=End[y];
Q[x].push_back(make_pair(y,i));
}
dfs_getans(0);
rep(i,1,m) printf("%d\n",Ans[i]);
}
}
HDU - 4117
這個就是dp從每一個的子串上轉移過來
轉移過程中不斷匹配,然后統計\(fail\)樹上前綴的最大值,這時一個動態的過程
於是我們樹剖線段樹
const int N=2e4+10,SIZE=2.3e5+10;
bool be;
int n,m;
string s[N];
int End[N],val[N];
int trie[SIZE][26],fail[SIZE],cnt;
void clear(){
memset(trie,0,sizeof (int) * (cnt+1)*26);
cnt=0;
}
int Insert(string &s){
int p=0;
rep(i,0,s.size()-1) {
//if(!isalpha(s[i])) while(1);
int x=s[i]-'a';
if(!trie[p][x]) trie[p][x]=++cnt;
p=trie[p][x];
}
return p;
}
int sz[SIZE],son[SIZE],top[SIZE],fa[SIZE];
struct Edge{
int to,nxt;
}e[SIZE];
int head[SIZE],ecnt;
void AddEdge(int u,int v){
e[++ecnt].to=v;
e[ecnt].nxt=head[u];
head[u]=ecnt;
}
void dfs1(int u){
sz[u]=1,son[u]=-1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!v) continue;
fa[v]=u;
dfs1(v);
sz[u]+=sz[v];
if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
}
}
int L[SIZE],R[SIZE],dfn;
//id[SIZE];
void dfs2(int u,int t){
top[u]=t;
L[u]=++dfn;
if(~son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!v||v==son[u]) continue;
dfs2(v,v);
}
R[u]=dfn;
}
void chk(int &a,int b){ ((a<b)&&(a=b)); }
struct SGT{
int sum[SIZE<<2];
void clear() { memset(sum,0,sizeof sum); }
void Upd(int p,int l,int r,int x,int y){
chk(sum[p],y);
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) Upd(p<<1,l,mid,x,y);
else Upd(p<<1|1,mid+1,r,x,y);
}
int Que(int p,int l,int r,int ql,int qr){
if(l==ql&&r==qr) return sum[p];
int mid=(l+r)>>1;
if(qr<=mid) return Que(p<<1,l,mid,ql,qr);
else if(ql>mid) return Que(p<<1|1,mid+1,r,ql,qr);
else return max(Que(p<<1,l,mid,ql,mid),Que(p<<1|1,mid+1,r,mid+1,qr));
}
}tr;
void Build(){
static queue <int> que;
dfn=0;
memset(head,0,sizeof head);ecnt=0;
rep(i,0,25) if(trie[0][i]) {
que.push(trie[0][i]);
fail[trie[0][i]]=0;
}
while(!que.empty()){
int u=que.front(); que.pop();
AddEdge(fail[u],u);
rep(i,0,25) {
int &v=trie[u][i];
if(v) {
que.push(v);
fail[v]=trie[fail[u]][i];
} else v=trie[fail[u]][i];
}
}
fa[0]=-1,dfs1(0);dfs2(0,0);
}
bool ed;
int main(){
//cout<<&ed-&be<<endl;
ios::sync_with_stdio(false);
int T; cin>>T;
rep(kase,1,T) {
clear();
tr.clear();
cin>>n;
rep(i,1,n) {
cin>>s[i]>>val[i];
End[i]=Insert(s[i]);
}
Build();
int ans=0;
rep(i,1,n) {
int res=0,p=0;
rep(j,0,s[i].size()-1) {
p=trie[p][s[i][j]-'a'];
int x=p;
while(~x) {
chk(res,tr.Que(1,1,dfn,L[top[x]],L[x]));
x=fa[top[x]];
}
}
chk(res,res+val[i]);
tr.Upd(1,1,dfn,L[End[i]],res);
chk(ans,res);
string t;swap(t,s[i]);
}
printf("Case #%d: %d\n",kase,ans);
}
}
HDU - 6096
為什么我覺得這個題是最難的
我不曉得官方做法,於是參考一笑網上神仙的思想
把要匹配的串換過來中間加上一個奇怪的字符再相連
原字符串也復制一份,中間加上一個奇怪的字符,然后就可以直接求匹配了!
由於不能重復,所以會受到長度的限制,用樹狀數組統計
const int N=1e5+10,K=20,SIZE=1.5e6;
const char d='`';
int n,m;
string s[N],a,b;
int trie[SIZE][27],cnt,len[N],fail[SIZE];
vector <int> vec[SIZE],Q[SIZE];
int End[N];
struct Edge{
int to,nxt;
}e[SIZE];
int head[SIZE],ecnt;
void AddEdge(int u,int v){
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
void clear(){
memset(trie,0,sizeof (int) * (cnt+1) *27 );
memset(head,0,sizeof (int) * (cnt+1));
rep(i,0,cnt) vec[i].clear(),Q[i].clear();
cnt=0;
}
int Insert(string &s){
int p=0;
rep(i,0,s.size()-1) {
int x=s[i]-d;
if(!trie[p][x]) trie[p][x]=++cnt;
p=trie[p][x];
}
return p;
}
void Build(){
static queue <int> que;
rep(i,0,26) if(trie[0][i]) {
que.push(trie[0][i]);
fail[trie[0][i]]=0;
}
while(!que.empty()) {
int u=que.front(); que.pop();
AddEdge(fail[u],u);
//cout<<u<<endl;
rep(i,0,26) {
int &v=trie[u][i];
if(v) {
fail[v]=trie[fail[u]][i];
que.push(v);
} else v=trie[fail[u]][i];
}
}
}
int Ans[N];
int sum[N];
void Add(int p,int x){
while(p) sum[p]+=x,p-=p&-p;
}
int Que(int p){
int res=0;
while(p<N) res+=sum[p],p+=p&-p;
return res;
}
void dfs(int u) {
//cout<<"#"<<u<<endl;
rep(i,0,Q[u].size()-1) Ans[Q[u][i]]=-Que(len[Q[u][i]]*2-1);
rep(i,0,vec[u].size()-1) {
Add(vec[u][i],1);
//cout<<"Add "<<vec[u][i]<<endl;
}
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
dfs(v);
}
rep(i,0,Q[u].size()-1) Ans[Q[u][i]]+=Que(len[Q[u][i]]*2-1);
}
int main(){
ios::sync_with_stdio(false);
int T;cin>>T;
rep(kase,1,T) {
cin>>n>>m;
clear();
rep(i,1,n) {
cin>>s[i];
s[i]=s[i]+d+s[i];
}
rep(i,1,m) {
cin>>a>>b;
b=b+d+a;
len[i]=b.size();
Q[Insert(b)].push_back((int)i);
}
Build();
rep(i,1,n) {
int p=0,len=s[i].size();
rep(j,0,len-1) {
p=trie[p][s[i][j]-d];
//cout<<"to pos "<<p<<endl;
if(p) vec[p].push_back(len);
}
}
memset(sum,0,sizeof sum);
dfs(0);
rep(i,1,m) printf("%d\n",Ans[i]);
}
}