UVALive 3942 Remember the Word(字典树+dp)

题意:

给定一个长度不超过30000的字符串 S ,然后给定n(n<=4000)个长度不超过100的字符串ai,问用 ai 组合成 S 有多少种方案数,由于数量可能很大,最终结果mod 20071027。

题解:

本来想学习一下字典树的,但是这题用到了dp。
dp[i]表示从 i 开始到S末尾有多少种组合方案。
那么dp公式为: dp[i]=dp[i+len(x)] (x表示已经存在的字符串)。
所以我们可以从后往前递推,枚举 dp[i] ,然后再枚举中断点判断 [i,j] 的单词是否出现过,最后 dp[0] 就是最终答案。

但是如果直接暴力,会超时,所以我们需要用字典树保存 ai ,每次查找单词是否出现就在字典树中查询,字典树查询的复杂的就是O(h),h为字符串的长度。

字典树:
向字典那样讲字符串保存在树当中,其中ch[i][j]表示在i状态下,输入字符j时,下个状态的位置是多少(类似于邻接表);val[i]表示i状态的时候是否是一个字符串的终点(也可以记录在这个位置终结的字符串的个数)。

其中ch[i][j]中j范围为字符集范围,例如小写字母是26个,0<=j<26;i比较大,最坏的情况下为 len(str)max{len(ai)} ,这样很可能超出内存,不过这种情况基本不会出现,如果内存不够可以开更小的数组或者改用指针形式的字典树。

my code

#pragma comment(linker, "/STACK:102400002,102400000")
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int MOD = 20071027;
const int MAXN = 4005 * 100 + 100;

int ch[MAXN][26];
int val[MAXN];
int sz;

struct Trie {

    Trie() {sz = 1; memset(ch[0], 0, sizeof(ch)); }
    int idx(char c) { return c - 'a'; }

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

    bool find(char* s, int len) {
        int u = 0;
        for(int i = 0; i <= len; i++) {
            int c = idx(s[i]);
            if(!ch[u][c])
                return false; 
            u = ch[u][c];
        }
        return val[u];
    }
};

char s[300005], word[105];
ll dp[300005];
int n;

int main() {
    int cas = 1;
    while(scanf("%s", s) != EOF) {
        Trie trie;
        scanf("%d", &n);
        for(int i = 0; i < n; i++) {
            scanf("%s", word);
            trie.insert(word);
        }

        memset(dp, 0, sizeof(dp));
        int len = strlen(s);
        dp[len] = 1;
        for(int i = len-1; i >= 0; i--) {
            for(int j = 0; j < 100 && i + j < len; j++) {
                if(trie.find(s + i, j)) {
                    dp[i] = (dp[i+j+1] + dp[i]) % MOD;
                }
            }
        }
        printf("Case %d: %lld\n", cas++, dp[0]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值