LA 3942 Remember the Word

      已知一些单词,选择其中一些单词组成目的字符串,问共有多少种方法。其实初看到这道题,自然而然地可以想到动态规划中经典的硬币问题:例如,问1元,2元,5元,总共有多少种方法能组成20元?这里不过是把硬币换成了单词而已。但是,如果真的只是像硬币问题一样每个单词都轮询一遍,显然太慢了,最多要有300000*4000*100次比对。

       假如利用trie数的话,至多只要比对100次,就能找到所有匹配的单词。然后将字符串从左至右DP即可。设d[i]表示从位置i开始的后缀的解,已知d[i]~d[n],那么求d[i-1]的话,只要找出能和suffix[i-1....n]的前缀匹配的单词,设单词长度为len,则d[i-1]+=d[i-1+len]即可,将所有匹配的单词找出来即可。

      最后,不要忘记一个Case结束之后,要重置trie树。


#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#define MAX         300000+5
#define MAXS        4000+5
#define MAXL        100+5
#define maxnode     4000*100
#define sigma_size  26
#define MOD         20071027
using namespace std;

struct trie{
    int ch[maxnode][sigma_size];
    int val[maxnode];
    int sz;
    trie(){sz=1;memset(ch[0],0,sizeof(ch[0]));}
    int idx(char c) {return c-'a';}

    void Insert(char*s,int num)//trie树中插入新单词
    {
        int u=0,n=strlen(s);
        for(int i=0;i<n;++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]=num;
    }

    vector<int> check(char*s){
        vector<int> ans;
        int u=0,n=strlen(s);

        for(int i=0;i<n;++i){
            int c=idx(s[i]);
            if(!ch[u][c]){//不匹配则退出
                return ans;
            }
            else{
                u=ch[u][c];//有匹配的单词则插入相应单词的编号
                if(val[u]){ ans.push_back(val[u]);}
            }
        }
        return ans;
    }

    void reset(){sz=1;memset(ch[0],0,sizeof(ch[0]));}//重置trie树
};

trie T;
int d[MAX],slen[MAX],S,Case;//slen存放相应编号的单词的长度
char line[MAX];


int main()
{
    while(cin>>line){
        cin>>S;
        T.reset();
        for(int i=1;i<=S;++i){
            char s[MAXL];
            cin>>s;
            slen[i]=strlen(s);
            T.Insert(s,i);
        }

        memset(d,0,sizeof(d));
        d[strlen(line)]=1;
        for(int i=strlen(line)-1;i>=0;--i){//从后往前DP
            vector<int> ans;
            ans=T.check(line+i);//找出所有匹配的单词的编号
            for(int j=0;j<ans.size();++j){
                d[i]=(d[i]+d[i+slen[ans[j]]])%MOD;
            }
        }

        cout<<"Case "<<++Case<<": "<<d[0]<<endl;
    }

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值