洛谷题单指南-动态规划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;
}

 

posted @ 2024-05-08 17:55  五月江城  阅读(32)  评论(0编辑  收藏  举报