[hdu2825][ac自动机][dp]Wireless Password

Wireless Password

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7689 Accepted Submission(s): 2498

Problem Description
Liyuan lives in a old apartment. One day, he suddenly found that there was a wireless network in the building. Liyuan did not know the password of the network, but he got some important information from his neighbor. He knew the password consists only of lowercase letters ‘a’-‘z’, and he knew the length of the password. Furthermore, he got a magic word set, and his neighbor told him that the password included at least k words of the magic word set (the k words in the password possibly overlapping).

For instance, say that you know that the password is 3 characters long, and the magic word set includes ‘she’ and ‘he’. Then the possible password is only ‘she’.

Liyuan wants to know whether the information is enough to reduce the number of possible passwords. To answer this, please help him write a program that determines the number of possible passwords.

Input
There will be several data sets. Each data set will begin with a line with three integers n m k. n is the length of the password (1<=n<=25), m is the number of the words in the magic word set(0<=m<=10), and the number k denotes that the password included at least k words of the magic set. This is followed by m lines, each containing a word of the magic set, each word consists of between 1 and 10 lowercase letters ‘a’-‘z’. End of input will be marked by a line with n=0 m=0 k=0, which should not be processed.

Output
For each test case, please output the number of possible passwords MOD 20090717.

Sample Input
10 2 2
hello
world
4 1 1
icpc
10 0 0
0 0 0

Sample Output
2
1
14195065

Source
2009 Multi-University Training Contest 1 - Host by TJU

Recommend
gaojie | We have carefully selected several similar problems for you: 2819 2824 2817 2823 2822

sol:

十分的惨惨啊,这是我人生中第一次写ac自动机上的dp,除练习外第一次正式接触ac自动机
感觉ac自动机上的dp似乎挺显然的

f[i][j][op]表示主串在i位置,在自动机的j节点,已经码出来了op的串的状态,然后瞎比转移就好了,复杂度O(n*26*m*mlen*2^10)所以就有了第一版最naive的做法

#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int pyz=20090717;
inline int read()
{
    char c;
    int res,flag=0;
    while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
    res=c-'0';
    while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
    return flag?-res:res;
}
const int N=110;
int tot,l,go[N][26],mark[N],tag[N],top[N][26];
char sr[N];
inline void insert(int &x,int y,int id)
{
    if(!x) x=++tot;
    if(y>l)
    {
        mark[x]=1<<id-1;
        return;
    }
    insert(go[x][sr[y]-'a'],y+1,id); 
} 
int q[N],fail[N];
inline void build()
{
    int u,v,t=0,w=1;
    q[1]=1;
    while(t<w)
    {
        u=q[++t];
        for(int i=0;i<=25;++i)
        if(go[u][i])
        {
            q[++w]=go[u][i];
            v=fail[u];
            while(!go[v][i]) v=fail[v];
            fail[go[u][i]]=go[v][i];
        }
    }


}
int f[26][N][1025],k;
int main()
{
    freopen("2825.in","r",stdin);
//  freopen("2825p.out","w",stdout);
    while(true)
    {
        n=read();
        m=read();
        k=read();
        if(!n&&!m&&!k) break;
        memset(f,0,sizeof(f));
        memset(go,0,sizeof(go));
        memset(mark,0,sizeof(mark));
        memset(fail,0,sizeof(fail));

        tot=1;
        int rt=1;
        for(int i=1;i<=m;++i)
        { 
            scanf("%s",sr+1);
            l=strlen(sr+1);
            insert(rt,1,i);
        }
        for(int i=0;i<=25;++i) go[0][i]=1;
        build();

        int all=1<<m;
        --all;
        f[0][1][0]=1;
        for(int i=0;i<n;++i)
        for(int j=1;j<=tot;++j)
        for(int op=0;op<=all;++op)
        if(f[i][j][op])
        for(int ch=0;ch<=25;++ch)
        {
            int b,c,d;
//          if(j==3&&ch==2)
//          cout<<"faq";
            b=j;
            while(!go[b][ch])
            b=fail[b];
            b=go[b][ch];
            c=op;
            d=b;
            while(d)
            {
                c|=mark[d];
                d=fail[d];
            }
    //      printf("%d %d %d %d\n",j,ch,b,c);
            (f[i+1][b][c]+=f[i][j][op])%=pyz;
        }
        int ans=0;
        for(int j=0;j<=tot;++j)
        for(int op=0;op<=all;++op)
        if(f[n][j][op])
        {
            int cnt=0;
            for(int i=0;i<m;++i)
            if((1<<i)&op) ++cnt;
            if(cnt>=k)
            (ans+=f[n][j][op])%=pyz;
        }
        printf("%d\n",ans);
    }
}

这个naive的算法t飞了,显然是因为我在dp里跳的原因。注意到跳的这个东西可以预处理,所以记top[u][ch]表示u,ch这个状态最后会跳到哪里,tag[u]表示mark[u]和mark[fail[u]]和mark[fail[fail[u]]]等等的mark值,转移也很simple

#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int pyz=20090717;
inline int read()
{
    char c;
    int res,flag=0;
    while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
    res=c-'0';
    while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
    return flag?-res:res;
}
const int N=260;
int tot,l,go[N][26],mark[N],tag[N],top[N][26];
char sr[N];
inline void insert(int &x,int y,int id)
{
    if(!x) x=++tot;
    if(y>l)
    {
        mark[x]=1<<id-1;
        return;
    }
    insert(go[x][sr[y]-'a'],y+1,id); 
} 
int q[N],fail[N];
inline void build()
{
    int u,v,t=0,w=1;
    q[1]=1;
    while(t<w)
    {
        u=q[++t];
        for(int i=0;i<=25;++i)
        if(go[u][i])
        {
            q[++w]=go[u][i];
            v=fail[u];
            while(!go[v][i]) v=fail[v];
            fail[go[u][i]]=go[v][i];
        }
    }

    for(int i=0;i<=25;++i) top[0][i]=1;
    //必须用bfs序来做,可能fail[u]>u 
//  for(int d=1;d<=tot;++d)
//  tag[d]=mark[d]|tag[fail[d]];

/*  for(int b=1;b<=tot;++b)
    for(int ch=0;ch<=25;++ch)
    {
        if(go[b][ch]) top[b][ch]=go[b][ch];
        else top[b][ch]=top[fail[b]][ch];
    }*/
    t=0;
    q[w=1]=1;
    while(t<w)
    {
        u=q[++t];
        int d=u;
        tag[d]=mark[d]|tag[fail[d]];
        for(int i=0;i<=25;++i)
        if(go[u][i])
        {
            q[++w]=go[u][i];
            int b=u,ch=i;
            top[b][ch]=go[b][ch];
        }
        else top[u][i]=top[fail[u]][i];
    }
}
int f[26][N][1025],k;
int main()
{
//  freopen("2825.in","r",stdin);
//  freopen("2825.out","w",stdout);
    while(true)
    {
        n=read();
        m=read();
        k=read();
        if(!n&&!m&&!k) break;
        memset(f,0,sizeof(f));
        memset(go,0,sizeof(go));
        memset(mark,0,sizeof(mark));
        memset(fail,0,sizeof(fail));
        memset(top,0,sizeof(top));
        memset(tag,0,sizeof(tag));

        tot=1;
        int rt=1;
        for(int i=1;i<=m;++i)
        { 
            scanf("%s",sr+1);
            l=strlen(sr+1);
            insert(rt,1,i);
        }
        for(int i=0;i<=25;++i) go[0][i]=1;
        build();

        int all=1<<m;
        --all;
        f[0][1][0]=1;
        for(int i=0;i<n;++i)
        for(int j=1;j<=tot;++j)
        for(int op=0;op<=all;++op)
        if(f[i][j][op])
        for(int ch=0;ch<=25;++ch)
        {
            int b,c,d;
            b=top[j][ch];
            c=op|tag[b];
            (f[i+1][b][c]+=f[i][j][op])%=pyz;
        }
        int ans=0;
        for(int j=0;j<=tot;++j)
        for(int op=0;op<=all;++op)
        if(f[n][j][op])
        {
            int cnt=0;
            for(int i=0;i<m;++i)
            if((1<<i)&op) ++cnt;
            if(cnt>=k)
            (ans+=f[n][j][op])%=pyz;
        }
        printf("%d\n",ans);
    }
}

这个代码也可以说是非常的naive了,因为显然两个bfs可以合并
就是写的时候注意一下一定要用bfs来算top和tag

#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int pyz=20090717;
inline int read()
{
    char c;
    int res,flag=0;
    while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
    res=c-'0';
    while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
    return flag?-res:res;
}
const int N=260;
int tot,l,go[N][26],mark[N],tag[N],top[N][26];
char sr[N];
inline void insert(int &x,int y,int id)
{
    if(!x) x=++tot;
    if(y>l)
    {
        mark[x]=1<<id-1;
        return;
    }
    insert(go[x][sr[y]-'a'],y+1,id); 
} 
int q[N],fail[N];
inline void build()
{
    int u,v,t=0,w=1;
    for(int i=0;i<=25;++i) top[0][i]=1;
    q[1]=1;
    while(t<w)
    {
        u=q[++t];
        int d=u;
        tag[d]=mark[d]|tag[fail[d]];
        for(int i=0;i<=25;++i)
        if(go[u][i])
        {
            q[++w]=go[u][i];
            v=fail[u];
            while(!go[v][i]) v=fail[v];
            fail[go[u][i]]=go[v][i];
            int b=u,ch=i;
            top[b][ch]=go[b][ch];
        }
        else top[u][i]=top[fail[u]][i];

    }
}
int f[26][N][1025],k;
int main()
{
//  freopen("2825.in","r",stdin);
//  freopen("2825.out","w",stdout);
    while(true)
    {
        n=read();
        m=read();
        k=read();
        if(!n&&!m&&!k) break;
        memset(f,0,sizeof(f));
        memset(go,0,sizeof(go));
        memset(mark,0,sizeof(mark));
        memset(fail,0,sizeof(fail));
        memset(top,0,sizeof(top));
        memset(tag,0,sizeof(tag));

        tot=1;
        int rt=1;
        for(int i=1;i<=m;++i)
        { 
            scanf("%s",sr+1);
            l=strlen(sr+1);
            insert(rt,1,i);
        }
        for(int i=0;i<=25;++i) go[0][i]=1;
        build();

        int all=1<<m;
        --all;
        f[0][1][0]=1;
        for(int i=0;i<n;++i)
        for(int j=1;j<=tot;++j)
        for(int op=0;op<=all;++op)
        if(f[i][j][op])
        for(int ch=0;ch<=25;++ch)
        {
            int b,c,d;
            b=top[j][ch];
            c=op|tag[b];
            (f[i+1][b][c]+=f[i][j][op])%=pyz;
        }
        int ans=0;
        for(int j=0;j<=tot;++j)
        for(int op=0;op<=all;++op)
        if(f[n][j][op])
        {
            int cnt=0;
            for(int i=0;i<m;++i)
            if((1<<i)&op) ++cnt;
            if(cnt>=k)
            (ans+=f[n][j][op])%=pyz;
        }
        printf("%d\n",ans);
    }
}

实际上ac自动机上的dp非常的easy啊,只要记得一次性把u和fail树祖先一起弄了就行,预处理一下一次跳到最后的数组,top和fail[0][x]全部设成1就行了。dp起来什么的全部都很轻松的样子啊(这里的flag我自己记下了啊,接下来可能还会写个2-3天的ac自动机dp)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值