题目描述
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行 N 个格子,每个格子上一个分数(非负整数)。棋盘第 11 格是唯一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中 M 张爬行卡片,分成 44 种不同的类型(M 张卡片中不一定包含所有 44 种类型的卡片,见样例),每种类型的卡片上分别标有 1,2,3,41,2,3,4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入格式
每行中两个数之间用一个空格隔开。
第 11 行 22 个正整数 N,M,分别表示棋盘格子数和爬行卡片数。
第 22 行 N 个非负整数,1,2,…,a1,a2,…,aN,其中 ai 表示棋盘第 i 个格子上的分数。
第 33 行 M 个整数,1,2,…,b1,b2,…,bM,表示 M 张爬行卡片上的数字。
输入数据保证到达终点时刚好用光 M 张爬行卡片。
输出格式
一个整数,表示小明最多能得到的分数。
输入输出样例
输入 #1
9 5 6 10 14 2 8 8 18 5 17 1 3 1 2 1
输出 #1
73
以下是我一开始写的方法,dp[i]的代表到达i格的各项状态,我没有意识到其实这样写无异于一个贪心,但显然这道题使用贪心是不对的,那么错在哪里呢?错在状态定义。正常来说,我们知道,动态规划其实就是记忆化搜索,而搜索就代表着你必须将所有可能的状态都走一遍,除非是已经证明完全不可能的路径才不需要去搜索。而我定义的这个状态,显然(其实也没那么显然),在没有排除任何可能的不可能路径情况下,并没有搜索到全部的状态,因为我是选取了最大值去作为状态转移的值的。我们来好好分析一下这道题。
#include<bits/stdc++.h>
using namespace std;
#define int long long
//一开始读题的时候就要想清楚,后面发现思路有误的话想改会很难受,就像是在打补丁
struct status
{
int all[5];//all[i]代表第i张卡片还剩几个
int val;
};
status dp[360];//dp[i]的代表到达i格的状态
bool cmp(pair<int, int>& a, pair<int, int>& b)
{
return a.second > b.second;
}
signed main()
{
int n, m;cin >> n >> m;
vector<int> board(n + 1);
for (int i = 1;i <= n;++i)
{
cin >> board[i];
}
for (int i = 0;i < m;i++)
{
int a;cin >> a;
dp[1].all[a]++;
}
dp[1].val = board[1];
for (int i = 2;i <= n;i++)
{
vector<pair<int, int>> v;
for (int j = 1;j <= 4;j++)
{
if (dp[i - j].all[j] > 0 && i - j > 0)
{
v.push_back(pair<int, int>(j, dp[i - j].val));
}
else continue;
}
if (!v.empty())
{
sort(v.begin(), v.end(), cmp);
for (int j = 1;j <= 4;j++)
dp[i].all[j] = dp[i - v[0].first].all[j];
dp[i].val = dp[i - v[0].first].val + board[i];
dp[i].all[v[0].first]--;
}
}
cout << dp[n].val;
return 0;
}
我的错误思路是:dp[i]的代表到达i格的各项状态,然后这个状态的分数,就等于在此之前的四格中能到达当前格的最大分数加当前格自带的分数。这样一看就能看出来是贪心 ,除非能证明贪心策略,否则不要乱“贪心”。因为到同样一个点可能有非常多的方式,而我们不知道选择哪一个方式最终能让我们得到最大值。正确的状态选取应该是:dp[a][b][c][d]代表四张卡片1,2,3,4已经分别使用了a,b,c,d张的时候所能获取的最大分数。乍一看要写一个四层循环,n^4的时间复杂度让人难以接受,但实际上这道题的卡片个数的数据范围并不大,只有四十,相当于最坏情况也就是遍历大约2.6e6次,这并不算太大。
不过这道题在思路上还是有点复杂,比较难想通的。下面是代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
//一开始读题的时候就要想清楚,后面发现思路有误的话想改会很难受,就像是在打补丁
int dp[45][45][45][45];
int val[360], card[5];
signed main()
{
int n, m;cin >> n >> m;
for (int i = 1;i <= n;i++)
cin >> val[i];
for (int i = 1;i <= m;i++)
{
int a;cin >> a;
card[a]++;
}
for (int a = 0;a <= card[1];a++)
{
for (int b = 0;b <= card[2];b++)
{
for (int c = 0;c <= card[3];c++)
{
for (int d = 0;d <= card[4];d++)
{
int ans = 0;
if (a)
ans = max(ans, dp[a - 1][b][c][d]);
if (b)
ans = max(ans, dp[a][b - 1][c][d]);
if (c)
ans = max(ans, dp[a][b][c - 1][d]);
if (d)
ans = max(ans, dp[a][b][c][d - 1]);
int i = a + 2 * b + 3 * c + 4 * d + 1;
ans += val[i];
dp[a][b][c][d] = ans;
}
}
}
}
cout << dp[card[1]][card[2]][card[3]][card[4]];
return 0;
}