题意:给出n个单词组成字典,再给出一个目标字符串,求把这个字符串分解成若干个单词的连接方案(字典中的单词可以重复使用)。
比如:输入 abcd, 再给出四个单词a, b, ab, cd。abcd可以分解为(a, b, cd), 或(ab, cd), 共两种方案
想法为:用dp[i]表示从i开始的后缀,则dp(i)=sum{d(i+len(x))|单词x是s[i...L]的前缀}
具体解释:以abcd为例,先把“a, b, ab, cd”建成一棵前缀树,
首先考虑后缀"d", 它的前缀为“d”, 然后去找字典中有没有这个前缀d,在树中询问,发现没有,所以用字典中单词分解“d”的方案为0;dp[3]=0;
再考虑“cd”, 前缀为“c, cd”。在树中询问,发现只有cd这个前缀,len["cd"]=2, dp[3]=dp[3+len[cd]]=dp[5]=1;
考虑“bcd”,前缀为“b, bc, bcd”, 只有前缀“b”, 那么dp[2]=dp[2+1]=1;
最后“abcd”, 前缀“a, b, ab, cd”, 有前缀“a”和“ab”,当固定“a”,存在的方案为"bcd"的分解方案,即从1开始的后缀的分解数目,为1;当固定“ab”时,存在的方案为“cd”的分解方案,即从2开始的后缀的分解方案数,为dp[2]=1;以上加起来,得dp[0]=2;
然后输出dp[0];
虽然白书上面有分析,搞了好久才看懂dp(i)=sum{d(i+len(x))}真心智商捉急,一把辛酸泪_(:з」∠)_ …………
觉得搞char型数组实在好麻烦,于是用了string来存字符串,一度担心过可能会T,不过还是过了,真是QvQ,点赞……
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxnode=500000;
const int sigma_size=27, mod=20071027;
int dp[maxnode];
struct Trie
{
int ch[maxnode][sigma_size];
int val[maxnode];
int sz;
void init()
{
sz=1;memset(ch[0], 0, sizeof(ch[0]));
}
int idx(char x)
{
return x-'a';
}
void insert(string &s, int v)
{
int u=0, n=s.size();
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]=v;
}
int query(string &s, int start)
{
int u=0, ret=0;
int len=s.size();
for(int i=start;i<len;i++)
{
int c=idx(s[i]);
u=ch[u][c];
if(u==0)return ret;
if(val[u])
{
ret+=dp[i+1];
ret%=mod;
}
}
return ret;
}
}trie;
int main()
{
string target, tmp;
int s;
int cnt=0;
while(cin>>target)
{
cnt++;
memset(dp, 0, sizeof(dp));
cin>>s;
trie.init();
for(int i=0;i<s;i++)
{
tmp.clear();
cin>>tmp;
trie.insert(tmp, 1);
}
int len=target.size();
dp[len]=1;
for(int i=1;i<=len;i++)
dp[len-i]=trie.query(target, len-i);
printf("Case %d: %d\n",cnt, dp[0]);
target.clear();
}
return 0;
}