题目链接
https://vjudge.net/problem/UVALive-3942
题意
给定一个字符串str和N个单词s。把这个字符串分解成若干个单词的连接(单词可以重复使用),有多少种方法?
解题
题目可以理解为由空串“走”到目标串str。每次走的“步数”恰好是一个单词。比如有单词a、b、cd、ab。目标串为abcd。可以由空串走到a,b,cd,ab。然后可以从a走到aa,ab,acd,aab.等等。
注意到从空串往目标串不太好,考虑从目标串走到空串。
字符串str[i…len]可以由字符串str[j…len]走到,当且仅当str[i…j-1]是一个单词。
而判断str[i…j-1]是否是一个单词,我们可以用str[i…len]去和字典树比较,数下有多少个单词是str[i…len]的前缀。(字典树又叫前缀树)。
设dp[i]表示str[i…len]的分解方法,那么
dp[i]=sigma d[i+len(x)],单词x是str[i…len]的前缀。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=4e3+7;
const int mod=20071027;
int ch[maxn*110][26],sz,ed[maxn*110];
int dp[300010],m;
char str[300010];
void init()
{
memset(ch[0],0,sizeof(ch[0]));
sz=0;
}
void insert(char *s)
{
int len=strlen(s),p=0;
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(!ch[p][c])
{
ch[p][c]=++sz;
ed[sz]=0;
memset(ch[sz],0,sizeof(ch[sz]));
}
p=ch[p][c];
}
ed[p]=1;
}
void query(int pos)
{
int len=m,p=0;
for(int i=pos;i<len;i++)
{
int c=str[i]-'a';
if(!ch[p][c]) return ;
p=ch[p][c];
if(ed[p]) dp[pos]=(dp[pos]+dp[pos+i-pos+1])%mod;
}
}
int main()
{
int kase=0;
while(~scanf("%s",str))
{
int n;
scanf("%d",&n);
init();
while(n--)
{
char s[110];
scanf("%s",s);
insert(s);
}
memset(dp,0,sizeof(dp));
m=strlen(str);
dp[m]=1;
for(int i=m-1;i>=0;i--)
query(i);
// for(int i=0;i<=m;i++)
// printf("%d %d\n",i,dp[i]);
printf("Case %d: %d\n",++kase,dp[0]);
}
return 0;
}