bzoj4460

bzoj 4460
提供一个n^3的做法.
首先考虑原题的性质,很容易知道答案不会超过目标串的长度,于是答案可以枚举。
然后我们可以枚举第一个字符是从第几行第几列开始的,然后之后的字典树暴力匹配.(n^4)
(一共L个字典树,字典树i存的是每个串从位置i开始的后缀。)

后来我发现其实第一个字符的列数可以不用枚举,我们先求出list[i][j]表示目标串中第i行能不能从给出的木桩中从位置j开始拼出,(枚举了答案之后可以发现,如果当前答案是i,那么1,1+i,1+2*i.../2,2+i.../之类的肯定在一行,所以每一行的字符就知道了,与实际想象的不同,我这里的第一行指的是1,1+i,2+i...,其实实际上这一行并不一定在你拼出木桩的第一行。然后还没有结束,因为要匹配成功必须同时让n行匹配上,我们发现一个匹配位置的序列a[1]...a[i],能够成功匹配出答案当且仅当只存在一个位置j(可以不存在),使得a[j]=a[j+1]...=a[i]=a[j-1]+1=a[j-2]+1...a[1]+1.,然后可以根据这个dp,设计状态f[i][j][0/1]表示第i行的串与位置j匹配,是否已经加了1,这个方案是否可行,转移就可以了。比如样例,zenit当答案枚举到3的时候第一行是zi,第二行是et,第三行是n,然后原来的木桩有tiet,oink,ezin,list[1][2]=true因为zi可以和第三个木桩从第二列开始匹配成功,同理list[2][3],list[3][3],list[3][4]都等于true。然后dp的时候一开始f[1][2][0]=true,转移方向是f[1][2][0]<--f[2][3][1]<--f[3][3][1].因为f[3][3][1]等于true所以说明答案等于3的时候有解。

(看那些几十毫秒的估计有n^2做法,但是我不会....)

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
int tot = 0,m,son[500010][27],n,root[310],_n;
char s[310],c[310];
string str[310];
bool list[310][310],f[310][310][2];
void insert(int &x,int y) {
	if(x == 0) x = ++tot;
	int Now = x;
	for(int i = y;i <= m;i ++)
	{
		int t = s[i] - 'a';
		if(son[Now][t] == 0)
		{
			son[Now][t] = ++tot;
			Now = son[Now][t];
		}
		else Now = son[Now][t];
	}
}  
int check(int Now,string x) {
	int len = x.size();
	for(int i = 1;i <= len;i ++) 
	{
		int t = x[i - 1] - 'a';
		if(son[Now][t]) Now = son[Now][t];
		else return false;
	}
	return true;
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i ++)
	{
		scanf("%s",s + 1);
		for(int j = 1;j <= m;j ++)
			insert(root[j],j);
	}
	scanf("%s",c + 1);
	_n = strlen(c + 1);
	for(int i = 1;i <= _n;i ++)
	{
		for(int k = 1;k <= i;k ++)
			str[k].clear();
		for(int j = 1;j <= i;j ++)
		{
			for(int k = j;k <= _n;k += i)
				str[j] += c[k];
		}
		for(int j = 1;j <= i;j ++)
		{
			for(int k = 1;k <= m;k ++)
			{
				if(check(root[k],str[j])) list[j][k] = true;
				else list[j][k] = false;
					
			}
		}
		memset(f,false,sizeof(f));
		for(int j = 1;j <= m;j ++) if(list[1][j]) f[1][j][0] = true;
		for(int j = 2;j <= i;j ++)
		{
			for(int k = 1;k <= m;k ++) 
				if(list[j][k])
				{
					if(f[j - 1][k][0]) f[j][k][0] = true;
					if(f[j - 1][k][1] || f[j - 1][k - 1][0]) f[j][k][1] = true;
				}
		}
		for(int j = 1;j <= m;j ++)
			if(f[i][j][0] || f[i][j][1])
				{cout<<i;return 0;} 
	}
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值