【洛谷P3146(USACO16OPEN)】248 G

文章介绍了如何使用区间动态规划(DP)解决一个与2048类似的游戏策略问题,其中合并规则是数值相等的相邻数字合并后加1。文章详细阐述了DP的五步法,包括状态表示、阶段划分、状态转移、边界设定和最终答案,并给出了状态转移方程和代码实现。此外,文章还提醒注意状态转移中的细节和可能的陷阱。
摘要由CSDN通过智能技术生成

作者:乐天JOE
题目链接


题目描述

Bessie likes downloading games to play on her cell phone, even though she doesfind the small touch screen rather cumbersome to use with her large hooves.

She is particularly intrigued by the current game she is playing.The game starts with a sequence of N N N positive integers ( 2 ≤ N ≤ 248 2 \leq N\leq 248 2N248), each in the range 1 … 40 1 \ldots 40 140. In one move, Bessie cantake two adjacent numbers with equal values and replace them a singlenumber of value one greater (e.g., she might replace two adjacent 7s with an 8). The goal is to maximize the value of the largest numberpresent in the sequence at the end of the game. Please help Bessiescore as highly as possible!

给定一个1*n的地图,在里面玩2048,每次可以合并相邻两个(数值范围1-40),问序列中出现的最大数字的值最大是多少。注意合并后的数值并非加倍而是+1,例如2与2合并后的数值为3。


输入格式

The first line of input contains N N N, and the next N N N lines give the sequence

of N N N numbers at the start of the game.


输出格式

Please output the largest integer Bessie can generate.


样例 #1

样例输入 #1

4
1
1
1
2

样例输出 #1

3

提示

In this example shown here, Bessie first merges the second and third 1s to

obtain the sequence 1 2 2, and then she merges the 2s into a 3. Note that it is

not optimal to join the first two 1s.



思路

这一题需要用到区间dp这一个算法,不会的可以先移步这一题:石子合并——这是区间dp的模板题。

看到题目:在一个 1 × n 1 \times n 1×n的地图。说明什么,这是1个 一维序列 ,也就是1条 线

那用的算法就很容易能看出来了,用区间dp

至于原因,是因为区间dp是在一条线上进行的,而且更好进行合并的步骤。

由于是2048游戏,所以还有1个隐藏条件:合并的两个数必须是相同且相邻的。(这点在翻译中没有体现)


来考虑dp的五个步骤。

1.状态表示:

区间 [i,j]

dp[i][j]表示的是:序列第i个数,放到第j个数区间内,全部合并的最大值。

2.阶段划分

由于是区间dp,那么显而易见,实际上我也不确定

小区间–>大区间

3.状态转移

合并代价:+1

i为左端点,j为右端点,k为断点

if(当前数字和下一个数字相同)
    dp[i][j]=max(dp[i][j],dp[i][k]+1);

这是本来的状态转移方程,有什么问题吗?

有,那就是dp[i][j]是全部合并而且可以不用相邻的合并,所以答案有可能是零或比较大的数像下面这几组数据:

样例1

3
7 8 7

样例2

3
1 2 3

第一个按照上面的状态转移方程答案会是9,因为不是相邻的两个7会合并。而第二个会是0,因为不能全部合并。

那我们就用一个值(ans)用来存合并后区间[i,j]内的最大数字

那正确的状态转移方程就很好写了:

if(当前数字和下一个数字相同)
{
    dp[i][j]=max(dp[i][j],dp[i][k]+1);
    ans=max(ans,dp[i][j]);
}

4.边界(初始值)设定

很简单,只需要把每个数设定为一个小区间,那么全部合并的最大答案就是那个数。

每次输入x,答案应该是区间[i,i]为这个数,其他为0:

for(int i=1;i<=n;i++)
{
    cin>>x;
    dp[i][i]=x;
}

5.最终答案

首先想到的是dp[1][n],但看到状态转移的部分,dp[1][n]是全部合并,而我们耗费了那么多的精力去创造的ans不是正好适合吗?

那么最后输出的答案就是ans。


坑点

这题有一些坑点:

  • 状态转移中说过的ans
  • 左端点与右端点的加一减一要搞清楚,不然要调很久,亲身经历
  • 搞清楚每个循环中每个变量是干嘛的,不要直接套模板

最后就可以开开心心的写代码了!


code

最后看代码

#include<bits/stdc++.h>
#define int long long
#define itn int
using namespace std;
int n,x,dp[255][255],ans; 
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++) //初始化
	{
		cin>>x;
		dp[i][i]=x;
	}
	for(int len=2;len<=n;len++) //len是遍历长度的,不需要从1开始
		for(int i=1;i<=n-(len-1);i++) //左端点
		{
			int j=i+len-1; //右端点
			for(int k=i;k<j;k++) //断点
				if(dp[i][k]==dp[k+1][j]) //如果左右相等(可以进行合并)
				{
					dp[i][j]=max(dp[i][j],dp[i][k]+1); //合并(也可以是dp[k+1][j]+1)
					ans=max(ans,dp[i][k]+1); //更新答案
				}
		}
	cout<<ans; //输出区间[1,n]的最大数字
   return 0;
}

AC记录
本人是个蒟蒻,学艺不精。如果有错误或建议请在评论区指出。

十分感谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值