AC自动机

有什么用:

处理多个字符串的问题

算法实现:

#include<bits/stdc++.h>
using namespace std;
string a[300010];
int num[300010],tr[300010][26],fail[300010],ans[300010];
int tmp,n,cnt;
void insert(string s,int id){
    int now=0;
    for(int i=0;i<s.size();i++){
        int o=s[i]-'a';
        if(!tr[now][o]) tr[now][o]=++cnt;
        now=tr[now][o];
    }
    num[now]=id;
}
void get_fail(){
    int now=0;
    queue<int> q;
    for(int i=0;i<26;i++)
        if(tr[0][i]){
            q.push(tr[0][i]);
            fail[tr[0][i]]=0;
        }
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            int v=tr[u][i];
            if(v){
            	fail[v]=tr[fail[u]][i];
                q.push(v);
            }
            else tr[u][i]=tr[fail[u]][i];
        }
    }
}
void query(string s){
    int now=0;
    for(int i=0;i<s.size();i++){
        now=tr[now][s[i]-'a'];
        for(int j=now;j;j=fail[j])
            ans[num[j]]++;
    }
}
int main(){
    ios::sync_with_stdio(false);
    while(cin>>n&&n){
        memset(num,0,sizeof(num));
        memset(ans,0,sizeof(ans));
        memset(tr,0,sizeof(tr));
        memset(fail,0,sizeof(fail));
        cnt=0;
        for(int i=1;i<=n;i++){
        	cin>>a[i];
        	insert(a[i],i);
        }
        get_fail();
        string k;
        cin>>k;
        query(k);
        tmp=0;
        for(int i=1;i<=n;i++)
            if(ans[i]>tmp) tmp=ans[i];
        cout<<tmp<<"\n";
        for(int i=1;i<=n;i++)
            if(ans[i]==tmp) cout<<a[i]<<"\n";
    }
    return 0;
}

题目:

P3121

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N=100007;
int ch[N][26], fail[N], val[N], cnt;
queue<int> q;
int n, top, que[N], ans[N];
char p[N], s[N];
void insert(char *s) {
    int len = strlen(s), now = 0;
    for (int i = 0; i < len; ++i) {
        int v = s[i] - 'a';
        if (!ch[now][v]) ch[now][v] = ++cnt;
        now = ch[now][v];
    }
    val[now] = len;
}
void get_fail() {
    for (int i = 0; i < 26; ++i)
        if (ch[0][i]) {
            q.push(ch[0][i]);
            fail[ch[0][i]] = 0;
        }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = 0; i < 26; ++i)
            if (ch[u][i]) {
                fail[ch[u][i]] = ch[fail[u]][i];
                q.push(ch[u][i]);
            } else ch[u][i] = ch[fail[u]][i];
    }
}
int main() {
    scanf("%s", s);
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%s", p);
        insert(p);
    }
    get_fail();
    int len = strlen(s), now = 0;
    for (int i = 0; i < len; ++i) {
        que[i] = now = ch[now][s[i] - 'a'];
        ans[++top] = i;
        if (val[now]) {
            top -= val[now];
            now = que[ans[top]];
        }
    }
    for (int i = 1; i <= top; ++i)
        putchar(s[ans[i]]);
    putchar('\n');
    return 0;
}

P2444

#include<bits/stdc++.h>
using namespace std;
const int N=33333;
int n,ch[N][2],fail[N],cnt;
bool is_word[N],vis[N],ins[N];
char s[N];
void insert(char *s){
    int len=strlen(s),now=0;
    for(int i=0;i<len;i++){
        int x=s[i]-'0';
        if(!ch[now][x]) ch[now][x]=++cnt;
        now=ch[now][x];
    }
    is_word[now]=1;
}
void get_fail(){
    queue<int> q;
    for(int i=0;i<=1;i++){
        if(ch[0][i]) q.push(ch[0][i]); 
    } 
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=0;i<=1;i++){
            int v=ch[now][i];
            if(v){
                fail[v]=ch[fail[now]][i];
                is_word[v]|=is_word[fail[v]];
                q.push(v); 
            }
            else ch[now][i]=ch[fail[now]][i];
        }
    }
}
void dfs(int now){
    if(ins[now]) puts("TAK"),exit(0);
    if(vis[now]||is_word[now]) return;
    ins[now]=vis[now]=1;
    dfs(ch[now][0]);
    dfs(ch[now][1]);
    ins[now]=0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        insert(s);
    }
    get_fail();
    dfs(0);
    puts("NIE");
    return 0;
} 

fail tree:

定义:按照AC自动机上fail的反向边建立的树

性质:一棵子树中所有的点都有子树的根这个后缀

应用:一个字符串在T(个体或集合)中出现了多少次

例题:

P2414

/*问题求的是x在y中出现了几次
建出fail tree后,求dfn序,记录每个节点的子树的dfn范围
每出现一个串i,在dfn[i]处+1,每撤销一个串i,在dfn[i]处-1
用树状数组,给节点x的子树求和,乃包含串x的子串个数*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=100010;
struct Edge{
    int u,v,nxt;
}edge[N];
int head[N],cnt;
struct Query{
    int x,y,ans,nxt;
}que[N];
int first[N],quetions;
int ch[N][26],fail[N],val[N],fa[N],vol[N],num,word;
int st[N],ed[N],tot;
int c[N];
char s[N];
int m;
void add_edge(int u,int v){
    edge[++cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
}
void add_que(int x,int y){
    que[++quetions].x=x;
    que[quetions].y=y;
    que[quetions].ans=0;
    que[quetions].nxt=first[x];
    first[x]=quetions;
}
void get_fail(){
    queue<int> q;
    for(int i=0;i<26;i++){
        if(ch[0][i]) q.push(ch[0][i]);
    }
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            if(ch[now][i]){
                fail[ch[now][i]]=ch[fail[now]][i];
                q.push(ch[now][i]);
            }
            else ch[now][i]=ch[fail[now]][i];
        }
    }
}
void dfs(int u){
    st[u]=++tot;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        dfs(v);
    }
    ed[u]=tot;
}
void build_fail_tree(){
    for(int i=1;i<=num;i++){
        add_edge(fail[i],i);
    }
    dfs(0);
}
inline int lowbit(int x){return x&(-x);}
void add(int x,int d){
    for(int i=x;i<=N-10;i+=lowbit(i)){
        c[i]+=d;
    }
}
int getsum(int x){
    int ans=0;
    for(int i=x;i>=1;i-=lowbit(i)){
        ans+=c[i];
    }
    return ans;
}
int main(){
    scanf("%s",s);
    int strl=strlen(s),now=0;
    for(int i=0;i<strl;i++){
        if(s[i]=='P'){
            val[now]=1;
            vol[++word]=now;
        }
        else if(s[i]=='B'){
            now=fa[now];
        }
        else{
            int x=s[i]-'a';
            if(!ch[now][x]){
                ch[now][x]=++num;
                fa[ch[now][x]]=now;
            }
            now=ch[now][x];
        }
    }
    get_fail();
    build_fail_tree();
    scanf("%d",&m);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        add_que(y,x);
    }
    now=0;word=0;
    for(int i=0;i<strl;i++){
        if(s[i]=='B'){
            add(st[now],-1);
            now=fa[now];
        }
        else if(s[i]=='P'){
            word++;
            for(int j=first[word];j;j=que[j].nxt){
                int l=st[vol[que[j].y]];
                int r=ed[vol[que[j].y]];
                que[j].ans=getsum(r)-getsum(l-1);
            }
        }
        else{
            int x=s[i]-'a';
            now=ch[now][x];
            add(st[now],1);
        }
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",que[i].ans);
    }
    return 0;
}

BZOJ 3881

/*需要注意的是,这里求的是包含x的串的个数,而非x在其中出现的次数
因此在一棵子树里同一种颜色可能有两个,对这棵子树的dfs序贡献必须是1
把当前所有要打上颜色的结点按dfs序排序,每个节点上+1,排序后相邻结点的lca出-1
这样对于一棵子树里的所有颜色相同的结点,它们总的贡献是1*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=2000003;
struct Edge{
    int nxt,to;
}edge[N];
int id[N],ch[N][26],fail[N],cnt2=1,head[N],cnt;
void add_edge(int u, int v){
    edge[++cnt]=(Edge){head[u],v}; 
    head[u]=cnt;
}
void insert(char *s,int num){
    int x,now=1,len=strlen(s);
    for(int i=0;i<len;i++){
        x=s[i]-'a';
        if(ch[now][x]!= 0) now=ch[now][x];
        else now=ch[now][x]=++cnt2;
    }
    id[num]=now;
}
void get_fail(){
    queue<int> q;
    q.push(1);
    while(!q.empty()) {
        int x=q.front();
        q.pop();
        for (int i = 0; i < 26; ++i)
            if(ch[x][i]){
                int v=ch[x][i];
                int f=fail[x];
                while(f&&ch[f][i]==0)
                    f=fail[f];
                fail[v]=f?ch[f][i]:1;
                q.push(v);
                add_edge(fail[v],v);
            }
    }
}
int n,tot=0,deep[N],st[N],ed[N],sz[N],top[N],son[N],fa[N];
char s[N];
void dfs(int x) {
    st[x]=++tot; 
    sz[x]=1;
    for(int i=head[x];i;i=edge[i].nxt) {
        int v=edge[i].to;
        fa[v]=x; 
        deep[v]=deep[x]+1;
        dfs(v); 
        sz[x]+=sz[v];
        if(son[x]==0||sz[v]>sz[son[x]])
            son[x]=v;
    }
    ed[x] = tot;
}
void dfs2(int x) {
    if(son[x]){
        top[son[x]]=top[x];
        dfs2(son[x]);
    }
    for(int i=head[x];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(v!=son[x])
            top[v]=v,dfs2(v);
    } 
}
int LCA(int x,int y){
    while(top[x]!=top[y]) {
        if(deep[top[x]]<deep[top[y]])
            swap(x, y);
        x=fa[top[x]];
    }
    return deep[x]<deep[y]?x:y;
}
bool cmp(int x,int y){
    return st[x]<st[y];
}
int bits[N],a[N];
void update(int x, int d) {
    for (;x<=tot;x+=(x&(-x)))
        bits[x]+=d;
}
int sum(int x) {
    int ret=0;
    for (;x;x-=(x&(-x)))
        ret+=bits[x];
    return ret;
}
void add(char *s){
    int len=strlen(s),x,now=1,tt=0;
    for (int i=0;i<len;i++){
        x=s[i]-'a';
        if(ch[now][x]) now=ch[now][x];
        else{
            while(now&&ch[now][x]== 0) now=fail[now];
            if(ch[now][x]) now=ch[now][x];
            else now=1;
        }
        a[++tt]=now;
        update(st[now],1);
    }
    sort(a+1,a+tt+1,cmp);
    for (int i=2;i<=tt;i++)
        update(st[LCA(a[i-1],a[i])],-1);
}
int Sum(int x) {
    return sum(ed[x])-sum(st[x]-1);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        insert(s,i);
    }
    get_fail();
    dfs(1);
    top[1]=1; 
    dfs2(1);
    int q,opt;
    scanf("%d",&q);
    while(q--){
        scanf("%d",&opt);
        if(opt==1) {
            scanf("%s",s);
            add(s);
        } 
        else{
            scanf("%d",&opt);
            printf("%d\n",Sum(id[opt]));
        }
    }
    return 0;
}

DP:

大部分f[i][j]表示当前在节点j,且串长为i时的情况

有时再加一维表示这个状态里面包含了哪些东西

题目:

P4052

//正难则反
#include<bits/stdc++.h>
using namespace std;
const int mod=10007;
const int N=100005;
int ch[N][26],fail[N],is[N];
int n,m,cnt,sum,ans,f[105][N];//f[i][j]表示当前在j点且串长为i时不经过单词结尾的路径条数
char s[N];
void insert(char *s){
    int len=strlen(s),now=0;
    for(int i=0;i<len;i++){
        int x=s[i]-'A';
        if(!ch[now][x]) ch[now][x]=++cnt;
        now=ch[now][x];
    }
    is[now]|=1;
}
void get_fail(){
    queue<int> q;
    for(int i=0;i<26;i++){
        if(ch[0][i]) q.push(ch[0][i]); 
    }
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            if(!ch[now][i]){
                ch[now][i]=ch[fail[now]][i];
                continue;
            }
            is[ch[now][i]]|=is[ch[fail[now]][i]];
            fail[ch[now][i]]=ch[fail[now]][i];
            q.push(ch[now][i]);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        insert(s);
    }
    get_fail();
    f[0][0]=1;
    for(int i=1;i<=m;i++){
        for(int j=0;j<=cnt;j++){
            for(int k=0;k<26;k++){
                if(!is[ch[j][k]])
                    f[i][ch[j][k]]+=f[i-1][j]%=mod;
            }
        }
    }
    for(int i=0;i<=cnt;i++)
        (ans+=f[m][i])%=mod;
    int sum=1;
    for(int i=1;i<=m;i++)
        sum=sum*26%mod;
    printf("%d",(sum-ans+mod)%mod);
    return 0;
}

P3041

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=510;
int ch[N][3],fail[N],num[N],cnt;
int n,k,dp[1005][N],vis[1005][N];
char s[N];
void insert(char *s){
    int len=strlen(s),now=0;
    for(int i=0;i<len;i++){
        int x=s[i]-'A';
        if(!ch[now][x]) ch[now][x]=++cnt;
        now=ch[now][x];
    }
    num[now]++; 
}
void get_fail(){
    queue<int> q;
    for(int i=0;i<3;i++){
        if(ch[0][i]) q.push(ch[0][i]);
    }
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=0;i<3;i++){
            int v=ch[now][i];
            if(v) fail[v]=ch[fail[now]][i],q.push(v);
            else ch[now][i]=ch[fail[now]][i];
        }
    }
}
int cal(int now,int val){
    while(now){
        val+=num[now];
        now=fail[now];
    }
    return val;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        insert(s);
    }
    get_fail();
    vis[0][0]=1;
    for(int i=0;i<k;i++){
        for(int j=0;j<=cnt;j++){
            if(!vis[i][j]) continue;
            for(int x=0;x<3;x++){
                int now=ch[j][x];
                dp[i+1][now]=max(dp[i+1][now],cal(now,dp[i][j]));
                vis[i+1][now]=1;
            }
        }
    }
    int ans=0;
    for(int i=0;i<=cnt;i++)
        ans=max(dp[k][i],ans);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值