[3015]挑战

挑战

Description

定义一个游戏,一些规则如下:
1、一个字符串X被称为字符串Y的同构串当且仅当构成X和Y的字母完全相同(不计较次序),如“baba”与“abab”,“aabb”,“abba”互为同构串。
2、一个字符串X被称为字符串Y的 子序列当且仅当X可以通过将Y去掉若干个字母而不改变相对顺序获得,如“ac”,“abd”,“abcd”都是“abcd”的子序列。
3、一个字符串X被称为字符串Y的同构子序列当且仅当存在一个字符串Z,使得X是Z的同构串,Z是Y的子序列。
现在要求把一个给定字符串S从中间分开成m个子串S1,S2,…,Sm,使得S1+S2+…+Sm=S,且对于任意i<m,Si是Si+1的同构子序列。求最大化m。

Input

第一行包含一个整数n,表示字符串的长度。
第二行为1个长度为n的字符串,仅包含大写字母。

Output

输出仅一行,一个整数,为最多可以分成的块数。

Sample Input

6
ABABAB

Sample Output

3

Hint

20%的数据:n≤10;
50%的数据:n≤100;
100%的数据:n≤500。

看到这题的时候我是懵逼的,数据很小题目也不复杂,可是本蒟蒻怎么看就是不会做,听了分析茅塞顿开。

有一个简单易想的算法,就是DP 设F[i][j]表示1~i中最后一块长度为j最多能分为多少块,并非满分算法。

这里,我们可以用贪心的思想让DP优化。

F[i]表示1~i最后一块长度最小值

则最后一块的下标为 i-j+1~i

倒数第二块的下标为 i-j-f[i-j]+1~i-j

通过判断最后一块是否为倒数第二块的同构子序列来进行状态转移。这就是判断的函数

bool check(int x,int y,int z)
{
	memset(b,0,sizeof(b));
	for(int i=y;i<=z;i++)
		b[s[i]-'A']++;
	for(int i=x;i<y;i++)
		b[s[i]-'A']--;
	for(int i=0;i<26;i++)
		if(b[i]<0)
			return false;
	return true;
}

由于我们希望序列越小越好,对后面越有利,因此枚举j的时候从小到大,发现可行的,就跳出循环。

int main()
{
	scanf("%d\n",&n);
	scanf("%s",s+1);
	for(int i=0;i<=1000;i++)
		f[i]=i+1;
	for(int i=2;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(check(i-j-f[i-j]+1,i-j+1,i))
			{
				f[i]=j;
				break;
			}
	ans=0;
	for(int i=n;i>=1;i-=f[i])
		ans++;
	printf("%d\n",ans);
	return 0;
}

有几点要注意,首先每个Fi都要赋初值i+1

其次注意跳出循环,我调试了好几次才发现这个问题。

答案的计算很巧妙,从F[n]向前推

下面贴上完整代码

#include "stdio.h"
#include "string.h"
#include "iostream"
#include "algorithm"
using namespace std;
int n;
char s[1005];
int f[1005];
int b[30];
int ans;
bool check(int x,int y,int z)
{
	memset(b,0,sizeof(b));
	for(int i=y;i<=z;i++)
		b[s[i]-'A']++;
	for(int i=x;i<y;i++)
		b[s[i]-'A']--;
	for(int i=0;i<26;i++)
		if(b[i]<0)
			return false;
	return true;
}
int main()
{
	scanf("%d\n",&n);
	scanf("%s",s+1);
	for(int i=0;i<=1000;i++)
		f[i]=i+1;
	for(int i=2;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(check(i-j-f[i-j]+1,i-j+1,i))
			{
				f[i]=j;
				break;
			}
	ans=0;
	for(int i=n;i>=1;i-=f[i])
		ans++;
	printf("%d\n",ans);
	return 0;
}

数组大小坑了我好几次,有点奇怪。

OVER~

祝我和学长99(✺ω✺)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值