【BZOJ1939】[Croatian2010] Zuma(动态规划)

博客介绍了如何使用动态规划解决BZOJ1939(Zuma)问题,讨论了状态定义和转移方程,包括三个关键的转移:c=k-1的状态代表、在前面插入同色弹子以及找一个相邻的同色弹子消去部分区间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

BZOJ1939(权限题)

分析:

这题很容易看出是DP,但是状态和转移都不是很好想……

d p [ l ] [ r ] [ c ] dp[l][r][c] dp[l][r][c]表示在 l l l前面已经新加了 c c c个和 l l l一样的弹子时,使区间 [ l , r ] [l,r] [l,r]消完所需插入的弹子数量
显然,当 c ≥ k − 1 c\geq k-1 ck1时,这 c c c个弹子和 l l l组成了连续的至少 k k k个弹子,这些情况是类似的(都可以一次消完)。因此可以用 c = k − 1 c=k-1 c=k1的状态代表所有 c ≥ k − 1 c\geq k-1 ck1的状态。
对于状态 ( l , r , k − 1 ) (l,r,k-1) (l,r,k1) l l l可以和前面 k − 1 k-1 k1个同色弹子一起消掉,因此只需要考虑要插入多少个才能完全消掉区间 [ l + 1 , r ] [l+1,r] [l+1,r]。这就得到第一个转移:(因为 [ l + 1 , r ] [l+1,r] [l+1,r]紧贴着 l l l l + 1 l+1 l+1左侧没有新插入的弹子,所以消掉 [ l + 1 , r ] [l+1,r] [l+1,r]所需插入的弹子数就是 d p [ l + 1 ] [ r ] [ 0 ] dp[l+1][r][0] dp[l+1][r][0]

d p [ l ] [ r ] [ k − 1 ] = d p [ l + 1 ] [ r ] [ 0 ] dp[l][r][k-1]=dp[l+1][r][0] dp[l][r][k1]=dp[l+1][r][0]

对于状态 ( l , r , c ) (l,r,c) (l,r,c),在前面插入一个 l l l的同色弹子就变成了 ( l , r , c + 1 ) (l,r,c+1) (l,r,c+1),所以比消完 ( l , r , c + 1 ) (l,r,c+1) (l,r,c+1)状态多一步,即:
d p [ l ] [ r ] [ c ] = d p [ l ] [ r ] [ c + 1 ] + 1 dp[l][r][c]=dp[l][r][c+1]+1 dp[l][r][c]=dp[l][r][c+1]+1

考虑对于弹子 l l l ,除了在它前面加 ( k − 1 ) (k-1) (k1)个同色弹子外,还可以找一个弹子 i ( i > l , a l = a i ) i(i>l,a_l=a_i) i(i>l,al=ai),先消去区间 [ l + 1 , i − 1 ] [l+1,i-1] [l+1,i1](该区间可能不存在),这样 i i i左侧就有 ( c + 1 ) (c+1) (c+1)个同色弹子,这就是状态 ( i , r , c + 1 ) (i,r,c+1) (i,r,c+1)。由此得到第三个转移:(注意特判 l + 1 = i l+1=i l+1=i时状态 ( l + 1 , i − 1 , 0 ) (l+1,i-1,0) (l+1,i1,0)不存在,以及 c + 1 ≥ k c+1\geq k c+1k时取 c = k − 1 c=k-1 c=k1
d p [ l ] [ r ] [ c ] = d p [ l + 1 ] [ i − 1 ] [ 0 ] + d p [ i ] [ r ] [ c + 1 ] ( l + 1 ≤ i − 1 ) dp[l][r][c]=dp[l+1][i-1][0]+dp[i][r][c+1](l+1\leq i-1) dp[l][r][c]=dp[l+1][i1][0]+dp[i][r][c+1](l+1i1)
d p [ l ] [ r ] [ c ] = d p [ i ] [ r ] [ c + 1 ] ( l + 1 = i ) dp[l][r][c]=dp[i][r][c+1](l+1=i) dp[l][r][c]=dp[i][r][c+1](l+1=i)

代码:

有了DP方程以后代码还是很好写的

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
namespace zyt
{
	const int N = 110, K = 7;
	int arr[N], dp[N][N][K];
	int work()
	{
		int n, k;
		ios::sync_with_stdio(false);
		cin.tie(0);
		cin >> n >> k;
		for (int i = 0; i < n; i++)
			cin >> arr[i];
		for (int i = 0; i < n; i++)
			for (int c = 0; c < k; c++)
				dp[i][i][c] = k - c - 1;
		for (int len = 2; len <= n; len++)
			for (int l = 0; l + len - 1 < n; l++)
			{
				int r = l + len - 1;
				for (int c = k - 1; c >= 0; c--)
				{
					if (c < k - 1)
						dp[l][r][c] = dp[l][r][c + 1] + 1;
					else
						dp[l][r][c] = dp[l + 1][r][0];
					if (arr[l] == arr[l + 1])
						dp[l][r][c] = min(dp[l][r][c], dp[l + 1][r][min(k - 1, c + 1)]);
					for (int i = l + 2; i <= r; i++)
						if (arr[l] == arr[i])
							dp[l][r][c] = min(dp[l][r][c], dp[l + 1][i - 1][0] + dp[i][r][min(k - 1, c + 1)]);
				}
			}
		cout << dp[0][n - 1][0];
		return 0;
	}
}
int main()
{
	return zyt::work();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值