【AcWing】蓝桥杯集训每日一题Day20|线性DP|312.乌龟棋(C++)

Source
312.乌龟棋
312. 乌龟棋 - AcWing题库
难度:简单
时/空限制:1s / 64MB
总通过数:5184
总尝试数:8091
来源:

《算法竞赛进阶指南》NOIP2010提高组
算法标签

DP线性DP

题目内容

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘只有一行,该行有 N 个格子,每个格子上一个分数(非负整数)。
棋盘第 1 格是唯一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中共有 M 张爬行卡片,分成 4 种不同的类型(M 张卡片中不一定包含所有 4 种类型的卡片),每种类型的卡片上分别标有 1、2、3、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。
游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。
玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

输入格式

输入文件的每行中两个数之间用一个空格隔开。
第 1 行 2 个正整数 N 和 M,分别表示棋盘格子数和爬行卡片数。
第 2 行 N 个非负整数,a1,a2,……,aN,其中 ai 表示棋盘第 i 个格子上的分数。
第 3 行 M 个整数,b1,b2,……,bM,表示 M 张爬行卡片上的数字。
输入数据保证到达终点时刚好用光 M 张爬行卡片。

输出格式

输出只有 1 行,包含 1 个整数,表示小明最多能得到的分数。

数据范围

1≤N≤350,
1≤M≤120,
0≤ai≤100,
1≤bi≤4,
每种爬行卡片的张数不会超过 40。

输入样例:

9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
输出样例:
73
题目解析

保证所有卡片用光的时候,刚好可以从1号格子跳到n号格子
使用的顺序可以任意取
每一个格子上有一个非负整数的分数
每走到一个格子,就可以把格子上的分数加起来
在所有跳的方案当中,总分的最大值是多少

总长度最大是350,总卡片数最多是120,每一种卡片最多40张

因为方案数非常多,所以用DP,而不是暴搜

  1. 直观定义状态的时候,肯定n有一维,四种卡片有多少张,各有一维
f[i][A][B][C][D]

这样一定会爆空间和时间

  1. 其实可以去掉一维,D可以计算
    f[i][A][B][C]D =(i - 1 - A - 2B - 3C)/4
    不过也很大,会超内存

  2. 这五维不是互相独立的,去掉D可以,去掉n也是可以的,因为n可以被算出来
    n = 1 + A + 2B + 3C + 4D
    状态数是41的4次方,可以接受

DP
状态表示

f[A][B][C][D]

  1. 表示哪个集合
    所有编号为1,2,3,4的卡分别使用了A,B,C,D张的方案的集合
  2. 存的是哪个属性
    每一个方案的分值的最大值

每个状态怎么算出来
对应的是集合的划分
要求集合中所有方案的最大值的话,一般是将这个集合划分成若干个子集,划分的时候一般是找最后一个不同点
1,2,3,4各使用A,B,C,D张的话,最后一张可以用1234中的任意一种,所以可以分成4大类,这样的方式可以将方案不重不漏的分为4类。
每种方案最后只能用1种数字,一定是没有重复的,任意两个子集之间一定是没有交集的,任意一个方案一定使用过最后一张,每一种方案一定是属于某一个子集的

如何求当前集合的最大值
只要分别求一下每一个子集的最大值,在整体取一个max,就得到整个集合的最大值了

  1. 每一个子集的最大值怎么求
    求第一个子集的最大值,从定义出发
  • 第一个子集表示从1号格子使用了A张1,B张2,C张3,D张4,使用完之后,跳到了某个格子,最后使用的是1,也就是最后一次跳了1步,的所有方案的集合
    最后一次用的是1,前面就是A-1张1,B张2,C张3,D张4
  • 第二个子集同上
    最后一次用的是2,前面就是A张1,B-1张2,C张3,D张4
  • 其余子集同理
  1. 每一类怎么求最大值
    前面的方案数是很多的,最后一步都一样,都是从某个格子跳到最后一个格子,最后一步的分值都是score
    对于第一类来说,最后一步的分值都一样,如果想求整个方案的最大值的话,应该让前面变化的部分取最大值

前面变化的部分,所有1234分别使用A-1,B,C,D张的所有方案的集合,要从这个集合当中取一个最大值,根据状态描述,恰好就是f[A-1][B][C][D]

加上最后一个格子的分值,就是第一类的最大值
f [ A − 1 ] [ B ] [ C ] [ D ] + s c o r e f[A-1][B][C][D]+score f[A1][B][C][D]+score
同理第二类
f [ A ] [ B − 1 ] [ C ] [ D ] + s c o r e f[A][B-1][C][D]+score f[A][B1][C][D]+score
第三类
f [ A ] [ B ] [ C − 1 ] [ D ] + s c o r e f[A][B][C-1][D]+score f[A][B][C1][D]+score
第四类
f [ A ] [ B ] [ C ] [ D − 1 ] + s c o r e f[A][B][C][D-1]+score f[A][B][C][D1]+score

f[A][B][C][D]的最大值等于这四种情况取max

代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 360, M = 41;

int n, m;
//w表示每个格子的分值,s表示1234的数量
int w[N], s[N];
int f[M][M][M][M];   //f是状态数组

int main()
{
	scanf("%d%d", &n, &m);
	//先读入每个格子的分值
	for (int i = 1; i <= n; i ++) scanf("%d", &w[i]);

	//读入m张卡片
	while (m --)
	{
		int x;
		scanf("%d", &x);
		s[x] ++;
	}

	//设置初值,一开始在起点,分值是1
	f[0][0][0][0] = w[1];
	//枚举一下所有状态
	for (int A = 0; A <= s[1]; A ++)
		for (int B = 0; B <= s[2]; B ++)
			for (int C = 0; C <= s[3]; C ++)
				for (int D = 0; D <= s[4]; D ++)
				{
					//先存跳到格子的分值
					int score = w[1 + A + B * 2 + C * 3 + D * 4];
					//把当前状态存下来
					int& v = f[A][B][C][D];
					//如果A是有的话,最后一张可以用A
					if (A) v = max(v, f[A-1][B][C][D] + score);
					if (B) v = max(v, f[A][B-1][C][D] + score);
					if (C) v = max(v, f[A][B][C-1][D] + score);
					if (D) v = max(v, f[A][B][C][D-1] + score);
				}
	printf("%d\n", f[s[1]][s[2]][s[3]][s[4]]);
	return 0;
}