前缀函数,AC自动机

前缀函数,AC自动机

洛谷p2375

前缀函数的应用。

题目大意:我们都知道前缀函数,但现在要求求出一个更强大num数组一一对于字符串S的前i个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作num[i]。

​ 其实只要找出最长符合要求前缀即可,更短的一定是最长符合要求前缀的前缀,推前缀函数时求一个“前缀”和就好(即累加到最长前缀上)。如何求最长不重合既是前缀又是后缀的串呢?前缀函数求的会包含重合的,想一想最开始重合那个过程,在i-1的位置如i=7,在i=6的位置p[i]一定等于3,这样i=7时才可能重合,重合时我们只需令 j = p [ j − 1 ] j=p[j-1] j=p[j1]即可。其他过程和求前缀函数一样,注意j设在外面,每次只用从上一次匹配的位置匹配,这样每次重合也最多重合一个字符,跳一会p[j-1]即可.

(啊,写出来好不清晰,如果掌握了前缀函数,这个还是可以想到的,至少最直观可以想到一直跳p[j-1]至不重合,用倍增处理可以降低时间复杂度,然而还是80,看题解有用倍增过的,还只是改了数组一维和二维的位置,好神奇。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int mod=1e9+7;
typedef long long ll;

int p[maxn],z[maxn],np[maxn],num[maxn],f[maxn][21];
char s[maxn];
void getpi(int n)
{
    p[0]=0,num[0]=0;
    for(int i=1;i<n;i++)
    {
        int j=p[i-1];
        while(j&&s[j]!=s[i]) j=p[j-1];
        if(s[i]==s[j]) j++;
        p[i]=j;
        if(j) num[i]=1+num[j-1];
        else num[i]=0;
    }
}

void pretreat(int n)
{
    for(int i=0;i<n;i++) f[i][0]=p[i];
    int l=0;
    while((1<<(l+1))<=n) l++;
    for(int i=1;i<=l;i++)
    {
        for(int j=1;j<n;j++)
        {
            if(f[j][i-1]-1)
            f[j][i]=f[f[j][i-1]-1][i-1];
            else f[j][i]=0;
        }
    }
}

ll solve(int n)
{
    np[0]=0;
    /*int l=0;
    while((1<<(l+1))<=n) l++;*/
    int j=0;
    for(int i=1;i<n;i++)
    {
        while(j&&s[j]!=s[i]) j=p[j-1];
        if(s[j]==s[i]) j++;
        if(2*j>i+1) j=p[j-1];
        /*int j=i;
        for(int k=l;k>=0;k--)
        {
            if(f[j][k]*2>i+1)
            {
                j=f[j][k]-1;
            }
        }
        if(j)
        j=f[j][0];*/
        if(j)
        np[i]=1+num[j-1];
        else np[i]=0;
    }
    ll ans=1;
    for(int i=0;i<n;i++)
    {
        ans*=(np[i]+1);
        ans%=mod;
    }
    return ans;
}

int main()
{
   // freopen("tte.txt","r",stdin);
    int n;scanf("%d",&n);
    while(n--)
    {
        scanf("%s",s);
        int l=strlen(s);
        getpi(l);
        //pretreat(l);
        ll ans=solve(l);
        printf("%lld\n",ans);
    }
    
    return 0;
}

牛客2020多校第二场A

​ 写了一遍忘保存了!

​ 利用前缀函数来容斥。要点:字符串哈希,把字符串所有后缀用map储存并对应好个数,那么枚举所有字符串所有前缀,计算对应个数,对应该前缀其长度为其前缀函数的前缀也统计过,但此时他们已不是最长,减去对应个数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int mod1=998244353;
//const ll mod2=1222827239;
const int x=2333;
const int maxn=1e5+5;
unordered_map<ull,int> op;
int id(char c)
{
    return c-'a'+1;
}
ull hx[maxn*10];
ull tx[maxn*10];
void getsufhx(char* s)
{
    int l=strlen(s);
    ull o=0;
    for(int i=0;i<l;i++)
    {
        o=o*x+id(s[i]);
        hx[i]=o;
    }
    for(int i=0;i<l;i++)
    {
        ll u=hx[l-1]-(i==0?0:hx[i-1])*tx[l-i];
      //  cout<<u<<endl;
        op[u]+=1;
    }
}
char s[maxn*25];
int sta[maxn],p[maxn*20];
int main()
{
    //freopen("tte.txt","r",stdin);
    int n;scanf("%d",&n);
    int st=0;
    tx[0]=1;
    for(int i=1;i<=1000001;i++)
    {
        tx[i]=tx[i-1]*x;
    }
    for(int i=1;i<=n;i++)
    {
        sta[i]=st;
        scanf("%s",s+st);
        getsufhx(s+st);                                                                                                     
        int l=strlen(s+st);
        char* ss=s+st;
        p[st]=0;
        for(int j=1;j<l;j++)
        {
            int k=p[st+j-1];
            while(k&&ss[k]!=ss[j]) k=p[st+k-1];
            if(ss[k]==ss[j]) k+=1;
            p[st+j]=k;
        }
        st+=l+1;
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        int l=strlen(s+sta[i]);
        char* ss=s+sta[i];
        ull tmp=0;
        for(int j=0;j<l;j++)
        {
            tmp=tmp*x+id(ss[j]);
           // cout<<tmp<<endl;
            ans+=op[tmp]*1ll*(j+1)%mod1*(j+1)%mod1;
            ans-=op[tmp]*1ll*p[sta[i]+j]%mod1*p[sta[i]+j]%mod1;
            ans=ans%mod1+mod1;
            ans%=mod1;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

HDU4758

题目大意:求一个长度为n+m的序列,只包含n个R,m个D两个字母,且给出两个R,D序列S,T,所求串必须包含S和T.

求这样的序列数。

​ AC自动机上的dp问题,同时还要表示已经出现了哪个要求子串,因为只有两个用两位二进制位即可表示所有状态,同时还需要记录已用几个R,几个D,在AC自动机哪个位置。之后逐一枚举即可,要先枚举状态,因为大的状态必然由小的状态推得。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=200+5;
const int sigma_size=2;
int ch[maxn][sigma_size],val[maxn],sz,m,n;
int idx(char c)
{
    return c!='D';
}

int d[maxn][105][105][4];
void init()
{
    sz=1;
    memset(ch[0],0,sizeof(ch[0]));
    val[1]=0;
  //  memset(vis,0,sizeof(vis));
}

void insert(char *s,int v)
{
    int u=0,len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int c=idx(s[i]);
        if(!ch[u][c])
        {
            memset(ch[sz],0,sizeof(ch[sz]));
            val[sz]=0;
            ch[u][c]=sz++;
        }
        u=ch[u][c];
    }
    val[u]=v;
}

int f[maxn],last[maxn];
queue<int> q;
void getfail()
{
    f[0]=0;
    for(int c=0;c<sigma_size;c++)
    {
        int u=ch[0][c];
        if(u)
        {
            f[u]=0;
            q.push(u);
            last[u]=0;
        }
    }

    while(!q.empty())
    {
        int r=q.front();q.pop();
        for(int c=0;c<sigma_size;c++)
        {
            int u=ch[r][c];
            if(!u)
            {
                ch[r][c]=ch[f[r]][c];
                continue;
            }
            q.push(u);
            int v=f[r];
            while(v&&!ch[v][c]) v=f[v];
            f[u]=ch[v][c];
            val[u]|=val[f[u]];
            last[u]=val[f[u]]?f[u]:last[f[u]];
        }
    }
}

char s[105],t[105];

int main()
{
    //freopen("tte.txt","r",stdin);
    int T;scanf("%d",&T);

    while(T--)
    {
        init();
        scanf("%d%d",&m,&n);
        scanf("%s%s",s,t);
        insert(s,1);
        insert(t,2);
        getfail();
        //int ans=solve(0,0,0,0);
        memset(d,0,sizeof(d));
        int ans=0;
        d[0][0][0][0]=1;
        for(int st=0;st<4;st++)
        {
            for(int k=0;k<=n;k++)
            {
                for(int l=0;l<=m;l++)
                {
                    for(int i=0;i<sz;i++)
                    {
                        if(d[i][k][l][st])
                        {
                            if(k<n)
                            d[ch[i][0]][k+1][l][st|val[ch[i][0]]]=(d[ch[i][0]][k+1][l][st|val[ch[i][0]]]+d[i][k][l][st])%mod;
                            if(l<m)
                            d[ch[i][1]][k][l+1][st|val[ch[i][1]]]=(d[ch[i][1]][k][l+1][st|val[ch[i][1]]]+d[i][k][l][st])%mod;  
                        }
                    }
                              
                }
            }
        }
        for(int i=0;i<sz;i++) 
        {
            ans+=d[i][n][m][3];
            ans%=mod;
        }
        printf("%d\n",ans);
    }

    return 0;
}

CSP20次认证第五题

​ 题目大意:csp题又臭又长。

​ 这题也是AC自动机上的dp,两个要求密文中不能出现字典中词:即不能经过AC自动机中单词节点且沿fail边到根的所有节点都不是单词节点。这题要记录长度,在日记本哪页,在AC自动机什么位置。之后还要枚举添加哪个串,注意给的是密文到明文,可以在输入时直接转换位明文到密文。先枚举长度,长的由短的推得,其他的随意。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=100+5;
const int sigma_size=26;
int ch[maxn][sigma_size],val[maxn],sz;
int mp[maxn][26],nxt[maxn][26];
int n,m;
char s[maxn][maxn];
int idx(char c)
{
    return c-'a';
}

void init()//啊啊啊,一定要调用一下
{
    sz=1;
    memset(ch[0],0,sizeof(ch[0]));
    memset(val,0,sizeof(val));
}

void insert(char *s,int v)
{
    int u=0,len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int c=idx(s[i]);
        if(!ch[u][c])
        {
            memset(ch[sz],0,sizeof(ch[sz]));
            val[sz]=0;
            ch[u][c]=sz++;
        }
        u=ch[u][c];
    }
    val[u]=v;//单词末尾节点设特殊值,也可统计次数等
}

int f[maxn],last[maxn];//后缀链接
queue<int> q;
void getfail()
{
    f[0]=0;
    for(int c=0;c<sigma_size;c++)
    {
        int u=ch[0][c];
        if(u)
        {
            f[u]=0;
            q.push(u);
            last[u]=0;
        }
    }

    while(!q.empty())
    {
        int r=q.front();q.pop();
        for(int c=0;c<sigma_size;c++)
        {
            int u=ch[r][c];
            if(!u)
            {
                ch[r][c]=ch[f[r]][c];
                continue;
            }
            q.push(u);
            int v=f[r];
            while(v&&!ch[v][c]) v=f[v];
            f[u]=ch[v][c];
            last[u]=val[f[u]]?f[u]:last[f[u]];
        }

    }
}


ll d[maxn*10][maxn][maxn];
int c=1,len[maxn];//长度,ac自动机位置,日记本页数

int go(int t,int& pos,int& now)
{
    int l=len[t];
    //cout<<pos<<' '<<now<<' '<<endl;
    for(int i=0;i<l;i++)
    {
        int id=idx(s[t][i]);
        int x=mp[now][id];
        // cout<<id<<' '<<x<<' '<<now<<' '<<pos<<endl;
        now=nxt[now][id];
        pos=ch[pos][x];
        if(val[pos]||last[pos]) return false;
    }
   // cout<<pos<<' '<<now<<endl<<endl;
    return true;
}

ll num[maxn*10];
int main()
{
    //freopen("tte.txt","r",stdin);
    scanf("%d%d",&n,&m);
    char cc[5];
    init();
    for(int i=0;i<26;i++)
    {
        for(int j=1;j<=n;j++)
        {
            int u;
            scanf("%s",cc);
            if(strlen(cc)==3)
            u=(cc[1]-'0')*10+(cc[2]-'0');
            else u=(cc[1]-'0');
            int v=cc[0]-'a';
            mp[j][v]=i;
            nxt[j][v]=u;
        }
    }
    
    while(scanf("%s",s[c])==1)
    {
        len[c]=strlen(s[c]);
        insert(s[c],1);
        c++;
    }
    c-=1;
    getfail();
    d[0][0][1]=1;
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<sz;j++)
        {
            for(int k=1;k<=n;k++)
            {
                if(d[i][j][k])
                {
                    for(int l=1;l<=c;l++)
                    {
                        if(i+len[l]<=m)
                        {
                            int now=k,pos=j;
                            bool ok=go(l,pos,now);
                            if(!ok) continue;
                            d[i+len[l]][pos][now]+=d[i][j][k];
                            d[i+len[l]][pos][now]%=mod;
                            num[i+len[l]]+=d[i][j][k];
                            num[i+len[l]]%=mod;
                        }
                    }
                }
            }
        }
    }
    for(int i=1;i<=m;i++)
    {
        printf("%lld\n",num[i]);
    }
    return 0;
} 

poj2778

AC自动机+矩阵快速幂

​ 居然是离散数学结论题:在离散数学,我们可以知道:要求出一个图中第i个点与第j个点的可达关系,我们可以通过将这张图的邻接矩阵A求T次幂,求幂的结果即为第i个点走T步达到结点j的路径个数。

于是用AC自动机预处理,于是题目转化位从根节点出发走n步且不经过单词节点的方案数。把节点间关系转化为邻接矩阵表示,利用矩阵快速幂n次方。 ∑ i = 0 n a n s [ 0 ] [ i ] \sum\limits_{i=0}^{n}ans[0][i] i=0nans[0][i]即为答案。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#include<queue>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<stack>
#include<map>
using namespace std;
using namespace std;
const int maxn=100+5;
const int mod=1e5;
const int sigma_size=4;
int ch[maxn][sigma_size],val[maxn],sz;
int mp[maxn][maxn];
int idx(char c)
{
    if(c=='A') return 0;
    if(c=='T') return 1;
    if(c=='C') return 2;
    if(c=='G') return 3;
}


void init()//啊啊啊,一定要调用一下
{
    sz=1;
    memset(ch[0],0,sizeof(ch[0]));
    memset(val,0,sizeof(val));
}

void insert(char *s,int v)
{
    int u=0,len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int c=idx(s[i]);
        if(!ch[u][c])
        {
            memset(ch[sz],0,sizeof(ch[sz]));
            val[sz]=0;
            ch[u][c]=sz++;
        }
        u=ch[u][c];
    }
    val[u]=v;//单词末尾节点设特殊值,也可统计次数等
}

int f[maxn],last[maxn];//后缀链接,上一个单词节点
queue<int> q;
int getfail()
{
    f[0]=0;
    for(int c=0;c<sigma_size;c++)
    {
        int u=ch[0][c];
        if(u)
        {
            f[u]=0;
            q.push(u);
            last[u]=0;
        }
    }

    while(!q.empty())
    {
        int r=q.front();q.pop();
        for(int c=0;c<sigma_size;c++)
        {
            int u=ch[r][c];
            if(!u)
            {
                ch[r][c]=ch[f[r]][c];
                continue;
            }
            q.push(u);
            int v=f[r];
            while(v&&!ch[v][c]) v=f[v];
            f[u]=ch[v][c];
            last[u]=val[f[u]]?f[u]:last[f[u]];
            val[u]|=val[f[u]];
        }

    }
}

char s[maxn];
int a[maxn][maxn],ans[maxn][maxn],tmp[maxn][maxn];
void martrix_mul(int a[][maxn],int b[][maxn],int n,int mod)
{
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=n;j++)
        {
            tmp[i][j]=0;
            for(int k=0;k<=n;k++)
            {
                tmp[i][j]+=1ll*a[i][k]*b[k][j]%mod;
                tmp[i][j]%=mod;
            }
        }
    }
    memcpy(a,tmp,sizeof(tmp));
}

void martrix_pow(int a[][maxn],int ans[][maxn],int y,int n,int mod)
{
    for(int i=0;i<=n;i++) ans[i][i]=1;
    while(y)
    {
        if(y&1)
        {
            martrix_mul(ans,a,n,mod);
        }
        y>>=1;
        martrix_mul(a,a,n,mod);
    }
}

int main()
{
    //freopen("tte.txt","r",stdin);
    int m,n;
    init();
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        insert(s,1);
    }
    getfail();

    for(int i=0;i<=sz;i++)
    {
        for(int j=0;j<4;j++)
        {
            if(!val[ch[i][j]])
            mp[i][ch[i][j]]+=1;
        }
        if(val[i])
        {
            memset(mp[i],0,sizeof(mp[i]));
        }
    }
    /*for(int i=0;i<=sz;i++)
    {
        for(int j=0;j<=sz;j++)
        {
            printf("%d ",mp[i][j]);
        }
        printf("\n");
    }*/
    martrix_pow(mp,ans,n,sz,mod);
    int res=0;
    for(int i=0;i<=sz;i++)//肯定从根出发啊
    {
        res+=ans[0][i];
        res%=mod;
    }
    printf("%d\n",res);
    return 0;
}

UVA11019

题目大意:给出一个n*m的字符矩阵T,你的任务是找出给定的x *y的字符矩阵P出现了多少次?

蓝书上一道题,利用AC自动机解决多模板匹配,同时有一些技巧,把P每一行拆出建立AC自动机,在用T的每一行来匹配,当P的第i行出现在T的第r行,起始列编号为c时,意味着 c o u n t [ r − i ] [ j ] count[r-i][j] count[ri][j]应该加1, c o u n t [ i ] [ j ] count[i][j] count[i][j]代表以(i,j)为左上顶点与P相同大小的矩阵与P匹配了多少行。

//please ac
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#include<queue>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<stack>
#include<map>
using namespace std;
typedef long long ll;
const int maxn=1000*1000+5;
const int sigma_size=26;
int ch[maxn][sigma_size],val[maxn],sz;
int f[maxn],last[maxn],nxt[1005];//后缀链接
int idx(char c)
{
    return c-'a';
}

void init()
{
    sz=1;
    memset(ch[0],0,sizeof(ch[0]));
    memset(val,0,sizeof(val));
}

void insert(char *s,int v)
{
    int u=0,len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int c=idx(s[i]);
        if(!ch[u][c])
        {
            memset(ch[sz],0,sizeof(ch[sz]));
            val[sz]=0;
            ch[u][c]=sz++;
        }
        u=ch[u][c];
    }
    nxt[v]=val[u];
    val[u]=v;//单词末尾节点设特殊值,也可统计次数等
}

int  n,m,x,y;
char T[1005][1005],s[105];
int  cnt[1005][1005];
queue<int> q;
void getfail()
{
    f[0]=0;
    for(int c=0;c<sigma_size;c++)
    {
        int u=ch[0][c];
        if(u)
        {
            f[u]=0;
            q.push(u);
            last[u]=0;
        }
    }

    while(!q.empty())
    {
        int r=q.front();q.pop();
        for(int c=0;c<sigma_size;c++)
        {
            int u=ch[r][c];
            if(!u)
            {
                ch[r][c]=ch[f[r]][c];
                continue;
            }
            q.push(u);
            int v=f[r];
            while(v&&!ch[v][c]) v=f[v];
            f[u]=ch[v][c];
            last[u]=val[f[u]]?f[u]:last[f[u]];
        }

    }
}

void solve(int c,int r,int h)
{
    for(int u=val[c];u!=0;u=nxt[u])
    {
        if(r-u+1>=1) cnt[r-u+1][h]++;
    }
}
void find(char *T,int r)//在T中找模板
{ 
    int len=strlen(T);
    int  j=0;
    for(int i=0;i<len;i++)
    {
        int c=idx(T[i]);
        j=ch[j][c];
        if(val[j]) 
        {
            solve(j,r,i+2-y);
        }
        else if(last[j])
        {
            solve(last[j],r,i+2-y);
        }
    }
}

int main()
{
    //freopen("tte.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int t;scanf("%d",&t);
    while(t--)
    {
        init();
        memset(cnt,0,sizeof(cnt));
        memset(nxt,0,sizeof(nxt));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%s",T[i]+1);
        scanf("%d%d",&x,&y);
        for(int i=1;i<=x;i++)
        {
            scanf("%s",s+1);
            insert(s+1,i);
        }
        getfail();
        for(int i=1;i<=n;i++)
        {
            find(T[i]+1,i);
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(cnt[i][j]==x) ans++;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}                           

fail树的应用:洛谷P2414

​ 把每次打印的串建立AC自动机,顺便建立fail树。查询第x个字符串在第y个字符串中出现了几次,其实就是找到fail树中x的单词节点的子树中有多少个点属于y.维护子树中内容,想到可以使用dfs序用树状数组或线段树维护。那么对于查询我们也可以用trie树来维护y,查询时就dfs trie树,进入单点更新给对应节点+1,退出单点更新-1,遇到单词节点查询对应x节点子树权值和,赋给对应查询答案。

​ 做本题时,到想到线段树维护dfs序都比较顺利,用trie维护查询想了挺久,代码敲了半天,但感觉实现起来还是有很多注意的,改的有点自闭,之后重新想一想再把代码补上吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值