洛谷题单指南-动态规划2-P1541 [NOIP2010 提高组] 乌龟棋
原题链接:https://www.luogu.com.cn/problem/P1541
题意解读:m张卡片,每张卡片数字1-4,不同的卡片排列,导致不同的走法,也产生不同的总分数,求最大分数。
解题思路:
首先想到的是暴力枚举,通过dfs枚举不同的卡片排列,然后不同的排列计算分数,求最大值
有两种方式实现排列:
1、枚举每一张卡片,根据每张卡片是否已经使用过进行递归排列
2、枚举卡片的数字1-4,根据当前数字的卡片是否有剩余来进行递归排列
由于相同数字的卡片有多张,第一种方法的排列势必导致重复,比如第一张、第二张卡片都是1,用第一种方法枚举会出现“第一张 第二张”“第二张 第一张”两种情况,而实际上对应的卡片数字都是1 1,因此采用第二种方式进行dfs,尽量减少重复。
30分代码:
#include <bits/stdc++.h>
using namespace std;
const int N= 400;
int n, m;
int a[N], b[5]; //a记录所有格子的分数,b[5]记录1~4卡片的张数
int ans;
//第k张卡片,第x格,当前总分是sum
void dfs(int k, int x,int sum)
{
if(k == m + 1 && x == n)
{
ans = max(ans, sum);
return;
}
for(int i = 1; i <= 4; i++) //遍历卡片数字1~4可以减少全排列的重复,如果是直接遍历每张卡片会导致较多的重复,更耗时
{
if(b[i] > 0)
{
b[i]--;
dfs(k + 1, x + i, sum + a[x + i]);
b[i]++;
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
int x;
for(int i = 1; i <= m; i++)
{
cin >> x;
b[x]++;
}
dfs(1, 1, a[1]);
cout << ans;
return 0;
}
DFS有没有优化的可能呢?是有的,可以借助于记忆化+剪枝
设f[i][j]记录用i个卡走到j格时的得分,这样每次dfs之前,判断如果本次i、j对应的f[i][j]已经存在且>=当前的得分,则可以停止dfs,因为在消耗了相同卡片数量且到达相同的格子,如果得分比之前的情况还少,后面肯定大概率是无法超越了。
50分代码:
#include <bits/stdc++.h>
using namespace std;
const int N= 400, M = 200;
int n, m;
int a[N], b[5]; //a记录所有格子的分数,b[5]记录1~4卡片的张数
int ans;
int f[M][N]; //f[i][j]记录用i个卡走到j的得分
//第k张卡片,第x格,当前总分是sum
void dfs(int k, int x,int sum)
{
if(f[k][x] >= sum) return; //如果得分比之前的小,不用继续
f[k][x] = sum;
if(k == m + 1 && x == n)
{
ans = max(ans, sum);
return;
}
for(int i = 1; i <= 4; i++) //遍历卡片数字1~4可以减少全排列的重复,如果是直接遍历每张卡片会导致较多的重复,更耗时
{
if(b[i] > 0)
{
b[i]--;
dfs(k + 1, x + i, sum + a[x + i]);
b[i]++;
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
int x;
for(int i = 1; i <= m; i++)
{
cin >> x;
b[x]++;
}
dfs(1, 1, a[1]);
cout << ans;
return 0;
}
本题的正解应该是动态规划,但是状态如何表示呢?
可以考虑最后一步的情况,最后一步,可能走1/2/3/4格,这样,可以直接把1、2、3、4对应的卡片张数作为状态!
状态表示:dp[i][j][k][l]表示使用i张1卡片,j张2卡片,k张3卡片,l张4卡片能得到的最大分数
状态计算:最后一步可能走1或2或3或4格,则有dp[i][j][k][l] = max(dp[i-1][j][k][l] + a[i*1+j*2+k*3+l*4+1],dp[i][j-1][k][l] + a[i*1+j*2+k*3+l*4+1], dp[i][j][k-1][l] + a[i*1+j*2+k*3+l*4+1]), dp[i][j][k][l-1] + a[i*1+j*2+k*3+l*4+1],i*1+j*2+k*3+l*4+1表示用完i、j、k、l张卡片后走到的位置,注意要判断i-1,j-1,k-1,l-1非负
初始值:dp[0][0][0][0][0] + a[1] , 一张卡片都不用,初始得分就是第一个格子的分数
结果:dp[b[1]][[b[2]][b[3]][b[4]] ,也就是消耗所有的1,2,3,4卡片能得到的最大分数
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N= 400, M = 45;
int n, m;
int a[N], b[5]; //a记录所有格子的分数,b[5]记录1~4卡片的张数
int dp[M][M][M][M]; //dp[i][j][k][l]表示使用i张1卡片,j张2卡片,k张3卡片,l张4卡片能得到的最大分数
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
int x;
for(int i = 1; i <= m; i++)
{
cin >> x;
b[x]++;
}
dp[0][0][0][0] = a[1];
for(int i = 0; i <= b[1]; i++)
{
for(int j = 0; j <= b[2]; j++)
{
for(int k = 0; k <= b[3]; k++)
{
for(int l = 0; l <= b[4]; l++)
{
int idx = i * 1 + j * 2 + k * 3 + l * 4 + 1; //用完i,j,k,l张卡片后走到的位置
if(i > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i-1][j][k][l] + a[idx]);
if(j > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j-1][k][l] + a[idx]);
if(k > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k-1][l] + a[idx]);
if(l > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k][l-1] + a[idx]);
}
}
}
}
cout << dp[b[1]][b[2]][b[3]][b[4]];
return 0;
}