题目地址:https://vjudge.net/problem/UVALive-3942
知识储备
Trie(前缀树/字典树):
以 a b cd ab 的顺序建立字典树,结果如下:
ch[i][j]表示结点i编号为j的子结点,val[i]表示结点i的附加信息(如,val[i]>0表示结点i是单词结点)。如ch[0][0] =1(0号节点下面的 0+'a' 节点的编号是1), ch[3][3] = 4(3号节点下面的 3+‘a’ 节点的编号是4。
题意
给出一个由S个不同单词组成的字典和一个长字符串。把这个字符串分解成若干个单词的连接(单词可以重复使用),有多少种方法?输出方法数除以20071027的余数
如,4个单词:a,b,cd,ab 则abcd有两种分解方法
解题思路
对给出的S个单词建立字典树,将给出的字符串反序,一定要反序!因为要找前缀!
dp[i]表示以i结尾的方案数,答案=dp[len(S)-1]
遍历反序后的字符串S,当前位置为i,遍历其前面的字符,j=i->0,状态转移方程如下:
a,b,c,ab建立的字典树如下:涂色的⭕️表示该结点是单词结点
ac代码
如果代码有错欢迎指出!
#include <iostream>
#include <algorithm>
#include <string.h>
#include <ctype.h>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <sstream>
const int maxn = 4e5+5;
using namespace std;
typedef long long ll;
int dp[300005],ch[maxn][30],val[maxn];
ll sz,S,mod=20071027;
char w[300005],s[105];
void build(char *s,int v)
{
ll u=0,n=strlen(s);
for(ll i=0;i<n;i++)
{
ll c=s[i]-'a';
if(!ch[u][c])
{
val[sz]=0;
ch[u][c]=sz++;
}
u=ch[u][c];
}
val[u]=v;//单词结点
}
int main()
{
// freopen("in.txt","r",stdin);
int Case = 0;
while(scanf("%s",w)!=EOF)
{
Case++;
memset(dp,0,sizeof(dp));
memset(ch,0,sizeof(ch));
memset(val,0,sizeof(val));
sz=1;
ll len=strlen(w);
reverse(w,w+len);//反序
//cout<<w<<endl;
scanf("%lld",&S);
for(int i=0;i<S;i++)
{
scanf("%s",s);
build(s,1);//建字典树
}
for(int i=0;i<len;i++)
{
ll u=0;//从根开始
for(int j=i;j>=0;j--)
{
ll c=w[j]-'a';
if(ch[u][c])
u=ch[u][c];
else
break;//结点u的子结点没有s[j]
if(!val[u]) continue;//不是单词结点,继续找
if(j==0)
dp[i]=(dp[i]+1)%mod;
else
dp[i]=(dp[i]+dp[j-1])%mod;
}
//cout<<dp[i]<<endl;
}
printf("Case %d: %d\n", Case, dp[len-1]);
}
return 0;
}