UVAlive 3942 - Remember the Word(字典树+dp或者AC自动机+dp)

Neal is very curious about combinatorial problems, and now here comes a problem about words. Knowing that Ray has a photographic memory and this may not trouble him, Neal gives it to Jiejie. Since Jiejie can’t remember numbers clearly, he just uses sticks to help himself. Allowing for Jiejie’s only 20071027 sticks, he can only record the remainders of the numbers divided by total amount of sticks. The problem is as follows: a word needs to be divided into small pieces in such a way that each piece is from some given set of words. Given a word and the set of words, Jiejie should calculate the number of ways the given word can be divided, using the words in the set.

Input

The input file contains multiple test cases. For each test case: the first line contains the given word whose length is no more than 300 000. The second line contains an integer S, 1 ≤ S ≤ 4000. Each of the following S lines contains one word from the set. Each word will be at most 100 characters long. There will be no two identical words and all letters in the words will be lowercase. There is a blank line between consecutive test cases. You should proceed to the end of file.

Output

For each test case, output the number, as described above, from the task description modulo 20071027.

Sample Input

abcd
4
a
b
cd
ab

Sample Output

Case 1: 2

 题目大意:给定一定长度的单词S,n个不同的单词,问用这n个单词组成s的方法种数。

第一种解法题目思路:设dp[i]表示以i结束的字符串分解的方法数。状态转移方程:dp[i]=sum(dp[i-len(x)]),x给定的n个单词长度。用字典树建立,如何依次枚举s的所有位置匹配即可。

代码:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<map>

using namespace std;

#define FOU(i,x,y) for(int i=x;i<=y;i++)
#define FOD(i,x,y) for(int i=x;i>=y;i--)
#define MEM(a,val) memset(a,val,sizeof(a))
#define PI acos(-1.0)

const double EXP = 1e-9;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const ll MINF = 0x3f3f3f3f3f3f3f3f;
const double DINF = 0xffffffffffff;
const int mod = 20071027;
const int N = 1e6+5;

int n,len;
char s[300005],a[4005][105];
int dp[300005];

const int MAXN = 400005;  //数组第一维大小,字符串的个数*最大长度
struct DicTree{
    int cnt[MAXN];  //代表某个前缀的单词总个数
    int tree[MAXN][26]; //注意修改这个儿子节点大小,全数字就是10,全小写字母就26
    int depth[MAXN];
    int tot;

    void init(){
        //特别说明多组数据才用init,否则一般不用,因为这种多个字符串输入一般都是一组数据
        MEM(cnt,0);
        MEM(tree,0);
        tot=0;
    }

    void Insert(char *ss){
        int len = strlen(ss);
        int now=0;
        for(int i=0;i<len;i++){
            int id = ss[i]-'a';
            if(!tree[now][id]){
                tree[now][id] = ++tot;
            }
            now = tree[now][id];
        }
        cnt[now]++;
        depth[now] = len;
    }

    void Search(char *s,int loc){
        int now=0;
        for(int i=loc-1;i<len;i++){
            int id = s[i]-'a';
            if(!tree[now][id])
                return ;
            now=tree[now][id];
            if(cnt[now]){
                dp[loc+depth[now]-1]+=dp[loc-1];
                dp[loc+depth[now]-1]%=mod;
            }
        }
    }
}dictree;

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    std::ios::sync_with_stdio(false);
    int T=0;
    while(~scanf(" %s",s+1)){
        ++T;
        printf("Case %d: ",T);
        MEM(dp,0);
        dictree.init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf(" %s",&a[i]);
            dictree.Insert(a[i]);
        }
        len = strlen(s+1);
        dp[0] = 1;
        for(int i=1;i<=len;i++){
            dictree.Search(s+1,i);
        }
        //for(int i=1;i<=len;i++)
            //printf("%d%c",dp[i],(i!=len)?' ':'\n');
        printf("%d\n",dp[len]%mod);
    }
    return 0;
}

第二种解法题目思路:设dp[i]表示以i开头的字符串分解的方法数。状态转移方程:dp[i]=sum(dp[i+len(x)]),x为S[i……L]的前缀。用字典树建立,如何依次枚举s的所有位置匹配即可。

代码:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<map>

using namespace std;

#define FOU(i,x,y) for(int i=x;i<=y;i++)
#define FOD(i,x,y) for(int i=x;i>=y;i--)
#define MEM(a,val) memset(a,val,sizeof(a))
#define PI acos(-1.0)

const double EXP = 1e-9;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const ll MINF = 0x3f3f3f3f3f3f3f3f;
const double DINF = 0xffffffffffff;
const int mod = 20071027;
const int N = 1e6+5;

int n,len;
char s[300005],a[4005][105];
ll dp[300005];

const int MAXN = 400005;  //数组第一维大小,字符串的个数*最大长度
struct DicTree{
    int cnt[MAXN];  //代表某个前缀的单词总个数
    int tree[MAXN][26]; //注意修改这个儿子节点大小,全数字就是10,全小写字母就26
    int tot;

    void init(){
        //特别说明多组数据才用init,否则一般不用,因为这种多个字符串输入一般都是一组数据
        MEM(cnt,0);
        MEM(tree,0);
        tot=0;
    }

    void Insert(char *s){
        int len = strlen(s);
        int now=0;
        for(int i=0;i<len;i++){
            int id = s[i]-'a';
            if(!tree[now][id]){
                tree[now][id] = ++tot;
            }
            now = tree[now][id];
        }
        cnt[now]++;
    }

    void Search(char *s,int loc){
        int now=0;
        for(int i=loc;i<len;i++){
            int id = s[i]-'a';
            if(!tree[now][id])
                return ;
            now=tree[now][id];
            if(cnt[now]==1){
                dp[loc]+=dp[i+1];
                dp[loc]%=mod;
            }
        }
    }
}dictree;

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    std::ios::sync_with_stdio(false);
    int T=0;
    while(~scanf(" %s",s)){
        ++T;
        printf("Case %d: ",T);
        MEM(dp,0);
        dictree.init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf(" %s",&a[i]);
            dictree.Insert(a[i]);
        }
        len = strlen(s);
        dp[len] = 1;
        for(int i=len-1;i>=0;i--){
            dictree.Search(s,i);
        }
        //for(int i=0;i<len;i++)
         //   printf("%d%c",dp[i],(i!=len-1)?' ':'\n');
        printf("%lld\n",dp[0]%mod);
    }
    return 0;
}

第三种做法:AC自动机+DP(dp代表和上面一样)

#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<map>

using namespace std;

#define FOU(i,x,y) for(int i=x;i<=y;i++)
#define FOD(i,x,y) for(int i=x;i>=y;i--)
#define MEM(a,val) memset(a,val,sizeof(a))
#define PI acos(-1.0)

const double EXP = 1e-9;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const ll MINF = 0x3f3f3f3f3f3f3f3f;
const double DINF = 0xffffffffffff;
const int mod = 20071027;
const int N = 5e5+5;

int len;
char s[N],a[N];
ll dp[N];

const int MAXN = 5e5+5;
struct Trie{
    int tree[MAXN][26];  //26是讨论全小写字母情况,根据题意修改
    int fail[MAXN];   //fail指针,匹配失败时返回位置
    int cnt[MAXN];    //cnt数组表示以该节点结束的字符串数量
    int root,tot;     //root是根节点,tot标记节点序号
    int depth[MAXN];

    int newnode(){
        for(int i=0;i<26;i++)
            tree[tot][i] = -1;
        depth[tot]=0;
        cnt[tot++] = 0;
        return tot-1;     //返回当前节点编号
    }

    void init(){
        tot = 0;
        root = newnode();
    }
    void Insert(char *s){
        int len = strlen(s);
        int now = root;
        for(int i=0;i<len;i++){
            int id = s[i]-'a';
            if(tree[now][id]==-1)   //无后继节点,新建节点
                tree[now][id] = newnode();
            now = tree[now][id];
        }
        cnt[now]++;
        depth[now] = len;
    }

    void build(){       //建立fail数组,构造失配指针
        queue<int>q;    //bfs寻找
        fail[root] = root;  //根节点的fail直接指向自己
        for(int i=0;i<26;i++){
            if(tree[root][i]==-1)
                tree[root][i] = root;
            else{           //根节点儿子的fail指针指向根节点
                fail[tree[root][i]]=root;
                q.push(tree[root][i]);
            }
        }
        while(!q.empty()){
            int now = q.front();
            q.pop();
            for(int i=0;i<26;i++){    //构造该节点的所有儿子fail指针
                if(tree[now][i]==-1)
                    tree[now][i] = tree[fail[now]][i];   //该段的最后一个节点匹配后,跳到拥有最大公共后缀的fail节点继续匹配
                else{
                    fail[tree[now][i]] = tree[fail[now]][i];   //当前节点的fail节点等于它前驱节点的fail节点的后继节点
                    q.push(tree[now][i]);
                }
            }
        }
    }

    void query(){
        //int len = strlen(s);
        int now = root;
        //int ans = 0;
        for(int i=1;i<=len;i++){
            int id = s[i]-'a';
            now = tree[now][id];
            int tmp = now;
            while(tmp != root){
                //ans+=cnt[tmp];   //加上以当前节点结尾的字符串数
                //cnt[tmp] = 0;    //可防止计算重复的字符串
                if(cnt[tmp]!=0)
                    dp[i] = (dp[i]+dp[i-depth[tmp]])%mod;
                tmp = fail[tmp]; //每次找最大公共后缀对应的fail节点
            }
        }
        //return ans;
    }
}ac;

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    std::ios::sync_with_stdio(false);
    int T=0,n,m;
    while(~scanf(" %s",s+1)){
        T++;
        printf("Case %d: ",T);
        ac.init();
        scanf("%d",&n);
        len = strlen(s+1);
        for(int i=1;i<=n;i++){
            scanf(" %s",a);
            ac.Insert(a);
        }
        ac.build();
        dp[0]=1;
        for(int i=1;i<=len;i++)
            dp[i]=0;
        ac.query();
        //for(int i=1;i<=len;i++)
         //   printf("%lld%c",dp[i],(i!=len)?' ':'\n');
         printf("%lld\n",dp[len]);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值