LA3942 remember the word trie+dp

题意:给出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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值