usaco2.3 prefix trie树 + dp

本文介绍了一种结合Trie树和动态规划的方法来解决字符串拼接问题,详细阐述了从前向后及从后向前两种DP策略,并通过实例解释了正确设置约束条件的重要性。

题目链接


题意:

给定一些 小 字符串,求得能由这些 小 字符串 拼接成的 大 字符串的 前缀 的最大值。


思路:

题目很直白显然,就是 trie 树 和 动态规划。


其实不用 trie 树也可以...但是怎么说呢

以前从来没写过 trie 树,想写一个试试看,于是就写了。

觉得这个时间还是挺有说服力的吧。(只是写得比较丑陋,请多多包涵

Executing...
   Test 1: TEST OK [0.000 secs, 5160 KB]
   Test 2: TEST OK [0.000 secs, 5160 KB]
   Test 3: TEST OK [0.000 secs, 5160 KB]
   Test 4: TEST OK [0.000 secs, 5160 KB]
   Test 5: TEST OK [0.000 secs, 5160 KB]
   Test 6: TEST OK [0.032 secs, 5164 KB]

All tests OK.

至于 dp 的部分,从前往后和从后往前都可以

从前往后,can[]记录从第0位到第 i 位是否可被构成。之后对于特定的 i, 向前找已经判断为可以构成的 j, 并且判断 j ~ i 的这一段是否也可被构成,一旦找到一个这样的 j, i就是可行的。

从后往前,dp[]要记录从当前第 i 位开始向后连续的长度。之后对于特定的 i, 向后找所有可以构成的段 i ~ j,然后再加上从 j 开始可以向后延伸的长度,取所有的这些 和 里面的最大值为dp[i]。


个人还是比较偏爱从前向后的,因为比较好想也比较直白嘛(个人见解

那么为什么会有从前往后呢???

因为一开始写跪了啊(叹 第 6 组数据总是超时,于是就 参考 了别人的 blog...

百思不得其解为什么那样的 dp 就可行,为什么我的 dp 就不行,明明时间复杂度的话我应该还更胜一筹的吧(大概

最后最后最后发现,dp时 我忘了加一个约束条件,那就是 

j ~ i 的这一段长度 <= 小字符串的最大长度

这是十分重要的,因为缺了这个条件的我的程序,会面临要去找一个长度为 20w 的字符串在不在 trie 树 中的窘境,而事实上在那个样例中,最大需要找的字符串的长度也不超过 10,真是辛苦它了。


所以说细节决定成败,一点不假。


 

/*
PROB: prefix
LANG: C++
ID: fan_0111
*/
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
struct node {
	char val;
	node* bro, *son;
	bool flag = false;
};
int tot = 0;
node* init(char ch) {
	tot++;
	node* p = new node;
	p->val = ch;
	p->bro = p->son = NULL;
	return p;
}
void add(node*& head, string s) {
	int len = s.length();
	node* fa = NULL, * temp = head;
	for (int i = 0; i < len; ++i) {
		if (head == NULL) {
			head = init(s[i]);
			if (tot == 1) temp = head;
			if (i == len-1) head->flag = true;
			if (fa) fa->son = head;
			fa = head;
			head = head->son;
			continue;
		}
		else {
			node* prev;
			while (head && head->val != s[i]) {
				prev = head;
				head = head->bro;
			}
			if (head) {
				fa = head;
				if (i == len-1) head->flag = true;
				head = head->son;
			}
			else {
				head = init(s[i]);
				if (i == len-1) head->flag = true;
				prev->bro = head;
				fa = head;
				head = head->son;
			}
		}
	}
	head = temp;
}
bool find(node* head, string s) {
	int len = s.length();
	for (int i = 0; i < len; ++i) {
		while (head && head->val != s[i]) head = head->bro;
		if (!head)					return false;
		if (i == len-1 && head->flag) return true;
		head = head->son;
	}
	return false;
}
bool can[200020];
int dp[200020];
int main() {
	freopen("prefix.in", "r", stdin);
	freopen("prefix.out", "w", stdout);
	string s;
	node* head = NULL;
	int maxLen = 0;
	while (cin >> s && s[0] != '.') {
		add(head, s);
		maxLen = max(maxLen, (int)s.length());
	}
	

	s = "";
	string line;
	while (cin >> line) s += line;
	
	int n = s.length();
	
//	for (int i = n-1; i >= 0; --i) {
//		for (int j = i; j-i+1 <= maxLen && j < n; ++j) {
//			if (!find(head, s.substr(i, j-i+1))) continue;
//			if (dp[i] < dp[j+1] + j - i + 1) dp[i] = dp[j+1] + j - i + 1;
//		}
//	}
//	cout << dp[0] << endl;
	
	int maxAns = -1;
	for (int i = 0; i < n; ++i) {
		for (int j = i; j >= 0 && i-j+1 <= maxLen; --j) {
			if ((can[j-1] || j == 0) && find(head, s.substr(j, i-j+1))) {
				can[i] = true;
				maxAns = max(maxAns, i);
				break;
			}
		}
	}
	
	cout << maxAns+1 << endl;

	fclose(stdin);
	fclose(stdout);
	return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值