题目
描述
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
格式
输入格式
输入文件的每行中两个数之间用一个空格隔开。
第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。
第2行N个非负整数,a1a2……aN,其中ai表示棋盘第i个格子上的分数。
第3行M个整数,b1b2……bM,表示M张爬行卡片上的数字。
输入数据保证到达终点时刚好用光M张爬行卡片。
输出格式
输出只有1行,1个整数,表示小明最多能得到的分数。
样例1
样例输入1[复制]
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
样例输出1[复制]
73
思路
这道题是当年在什么都不懂的情况下参加比赛做的,当时对DP一无所知orz
虽然这题不是特别复杂,不过还是想好好记录一下这题。
乌龟棋的状态比较复杂,每走一步显然都不是一个状态转移,而是由许多卡片一起决定的。
而且这种不像一般的DP可以用矩阵推一推,因为维数显然超过了2维,所以只能进行理论推导。
这道题的突破点在于只有1、2、3、4一共4种步数的卡片,这样使得整个步数及状态的转移控制在一个比较小的范围内。
我一开始所想,以一个4维数组dp[i][j][k][m]表示1、2、3、4的卡片分别**剩下**i,j,k,l张的时候走过的最大值。
注意我标重的“剩下”二字。
这个状态的定义实际上是很有问题的,因为剩下的卡片数和距离没有明确的关系,于是需要再另开一维数组
dp[p][i][j][k][l],表示在位置p的时候,卡片还剩下i,j,k,l的时候,所走过的最大距离
于是列出了如下式子:
dp[p][i][j][k][l]=max{dp[p-1][i-1][j][k][l],
dp[p-2][i][j-1][k][l]
dp[p-3][i][j][k-1][l]
dp[p-4][i][j][k][l-1]}+road[p];
嗯,看起来似乎很顺利…
但是!递推顺序不对啊,还剩i张的状态怎么会在i-1张状态的后面,卡片应当是越来越少的。
于是把i-1改成i+1?
dp[p][i][j][k][l]=max{dp[p-1][i+1][j][k][l],
dp[p-2][i][j+1][k][l]
dp[p-3][i][j][k+1][l]
dp[p-4][i][j][k][l+1]}+road[p];
还是有问题,p是从后往前推的,但是ijkl却是从前往后推的,二者方向相反。
所以会出现一个非常尴尬的情况是不知道从前往后推还是从后往前推好。
然后就没有然后了…
这道题选取状态上有一个很重要的一点是。
选取的卡片对应的步数总量==当前位置
这是个非常重要的点,通过这一点我们就可以将维数从5降低到4
但是如果dp[i][j][k][l]的ijkl还是表示剩下卡片数的话,还是很难以推出之间的关系。
考虑将ijkl表示已经用了多少卡片!!!
这样思路瞬间迎刃而解
我们以dp[i][j][k][l]表示已经用了1234的卡片各ijkl张的时候,所产生的最大价值。
于是仿照我们之前的写法可以很顺畅的写出DP方程
dp[i][j][k][l]=max{dp[i-1][j][k][l],
dp[i][j-1][k][l]
dp[i][j][k-1][l]
dp[i][j][k][l-1]}+road[i+2*j+3*k+4*l];
这样一来求取ijkl状态之前需要求出i-1,j-1等的状态,并且加上当前的位置【i+2*j+3*k+4*l】
于是正推就可以~
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<vector>
#include<limits>
#include<ctype.h>
#include<algorithm>
#define M 100
#define N 200
#define SN 41
using namespace std;
int dp[SN][SN][SN][SN];
int max(int a,int b){
return a>b?a:b;
}
class Solution{
public:
int generate(vector<int> &road,vector<int> &cards){
memset(dp,0,sizeof(dp));
//abcd表示1234的牌各有多少种
int a=cards[1],b=cards[2],c=cards[3],d=cards[4];
for(int i=0;i<=a;i++){
for(int j=0;j<=b;j++){
for(int k=0;k<=c;k++){
for(int l=0;l<=d;l++){
int val=0;
if(i>0)
val=max(val,dp[i-1][j][k][l]);
if(j>0)
val=max(val,dp[i][j-1][k][l]);
if(k>0)
val=max(val,dp[i][j][k-1][l]);
if(l>0)
val=max(val,dp[i][j][k][l-1]);
dp[i][j][k][l]=val+road[i+j*2+k*3+l*4];
}
}
}
}
return dp[a][b][c][d];
// memset(dp,0,sizeof(dp));
// //初始时刻获取第一格的分数
// dp[0][a][b][c][d]=road[0];
// int n=road.size();
// //遍历n个点
// for(int p=1;p<n;p++){
// //对于每一点,需要往后推4个位置,并更新那些位置的值
// for(int i=a;i>=0;i--){
// if(dp[p-1][i][j][k][l]>0)
// dp[p][i-1][j][k][l]=max(dp[p-1][i])
// for(int j=b;j>=0;j--){
// for(int k=c;k>=0;k--){
// for(int l=d;l>=0;l--){
// //可以扩展到4个位置
// int target_pos1=p+1*i;
// int target_pos2=p+2*j;
// int target_pos3=p+3*k;
// int target_pos4=p+4*l;
// dp[target_pos1][a-i][j][k][l]=dp[p][i][j][k][l]+road[target_pos1];
// }
// }
// }
// }
// }
}
};
/*9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1*/
int main(){
int n,m;
vector<int> road;
vector<int> temp,cards;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
int tempv;
scanf("%d",&tempv);
road.push_back(tempv);
}
for(int i=0;i<m;i++){
int tempv;
scanf("%d",&tempv);
temp.push_back(tempv);
}
//cards数组用于计算每一种卡片的数量
cards.resize(temp.size()+5);
for(int i=0;i<temp.size();i++)
cards[temp[i]]++;
Solution solution;
int val=solution.generate(road,cards);
printf("%d\n",val);
// system("pause");
return 0;
}