Zoj 3181 Cover the String (字符串_字典树(DP))

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3222


题目大意:给定一个模式串S,n个匹配串str,我们可以用匹配串覆盖模式串,每个匹配串可出现多次,每个前后相连的匹配串不能完全重叠,如果str[i]和str[j]相同,在算方案数时仍是不同的。strlen(S) <= 1000,n <= 200,strlen(str[i]) <= 200.


解题思路:第一次看到题目,没什么思路,其实是题目没看懂。后来再结合着样例把题目看懂了,觉得挺有规律的,但是规律怎么样想不到,也想不到怎么用KMP、或者字典树或者AC自动机。又看了下S的长度,可以用O(n^2)的复杂度过。然后就一直往这个复杂度的算法去想,觉得可以先建棵字典树,然后再进行操作。

    先不管题目要求的那么多匹配要求,能发现一个性质:匹配到i位置,那他肯定是从前面的某个j位置而来,可以是0,1..i-1,似乎好像的确满足无后效行,那似乎好像的确可以用动态规划来解。现在假设我们从S中的j位置(和前一句话意义相同)开始从字典树中遍历,遍历到某个节点是某个字符串结束节点,此时匹配到S中的i位置,说明这一段可以匹配,那么从j开始,j、j+1、j+1....i-1找之前匹配过的方法数,累加起来就是在i处正确匹配的方法数。

   dp[i]表示合法匹配到i位置的方法数,现在从j位置开始匹配,说明dp[j],dp[j+1],dp[j+2]...dp[i]都在之前j-1处匹配的时候计算过,那么本次dp[i]要怎么计算,可以让tot+=dp[j]、dp[j+1],dp[j+2]..dp[i-1],最后匹配到S的末尾后再加到dp[i].为什么不能用dp[i]直接加呢,因为要保证后效性,不然在算dp[i+1]的时候就乱套了。

    如果大家还是迷糊,用草稿模拟下我给的第一个测试数据的计算过程,

    答案是13.本题计算过程有可能超32位int,注意在计算的时候用上64位int。


测试数据:

10

AAAAA
4
A
AA
AAA
AAAA

AAAAAAAA
3
AA
AA
AA

AAAA
4
A
AA
AAA
AAAA


代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MIN 210
#define MAX 1100
#define MOD 1000000007


struct node {

	int  cnt;	
	node *next[26];
}*root;
int  n,dp[MAX];
char dir[MAX][MIN],str[MAX];


node *CreateNode() {

	node *p;
	p = (node *) malloc (sizeof(node));
	p->cnt = 0;
	for (int i = 0; i < 26; ++i)
		p->next[i] = NULL;
	return p;
}
void Release(node *p) {

	for (int i = 0; i < 26; ++i)
		if (p->next[i] != NULL) Release(p->next[i]);
	free(p);
}

void Insert(char *str) {

	int i = 0,k;
	node *p = root;


	while (str[i]) {

		k = str[i++] - 'A';
		if (p->next[k] == NULL)
			p->next[k] = CreateNode();
		p = p->next[k];
	}
	p->cnt++;
}
void Query(char *str){

	int i = 0,k;
	node *p = root;


	while (str[i]) {

		k = str[i] - 'A';
		if (p->next[k] == NULL)
			break;
		p = p->next[k];
		dp[i] = p->cnt;
		i++;
	}
}
void UpdateDP(char *str,int index) {

	int i = 0,k,j;
	long long tp[MAX],tot = 0;
	node *p = root;
	memset(tp,0,sizeof(tp));


	while (str[i]) {

		k = str[i] - 'A';
		if (p->next[k] == NULL)
			break;
		p = p->next[k];
		if (p->cnt)
			tp[i+index] = ((long long)(tot * p->cnt)) % MOD;
		tot = (dp[index+i] + tot) % MOD;
		i++;
	}
	for (j = 0; j <= i + index; ++j)
		dp[j] = (tp[j] + dp[j]) % MOD;
}


int main()
{
	int i,j,k,t;


	scanf("%d",&t);
	while (t--) {

		scanf("%s",str);
		scanf("%d",&n);
		root = CreateNode();
		for (i = 0; i < n; ++i){

			scanf("%s",dir[i]);
			if (strlen(dir[i]) == 1)
				continue;
			Insert(dir[i]);
		}


		memset(dp,0,sizeof(dp));
		Query(str);
		for (i = 1; str[i]; ++i)
			UpdateDP(str+i,i);
		printf("%d\n",dp[i-1]%MOD);
		Release(root);
	}
}


本文ZeroClock原创,但可以转载,因为我们是兄弟。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值