【leetcode】【2023/6/24】1659. 最大化网格幸福感

问题描述:

  • 给定一个 m × n m\times n m×n 的二维网格,并且给定 i c ic ic 个内向的人和 e c ec ec 个外向的人并尝试将这些人放置到网格中。
    • 不需要所有人都在网格中。
    • i i i 人放置在网格中就会加 120 120 120 幸福感,但每存在一个邻居都会失去 30 30 30 幸福感。
    • e e e 人放置在网格中就会加 40 40 40 幸福感,且每存在一个邻居都会得到 20 20 20 幸福感。
  • 返回最大可能的幸福感总和。
  • 满足以下条件:
    • 1 ≤ m , n ≤ 5 1\leq m,n\leq 5 1m,n5
    • 0 ≤ i c , e c ≤ min ⁡ ( m ∗ n , 6 ) 0\leq ic,ec\leq \min(m*n,6) 0ic,ecmin(mn,6)

解题思路:

  • 该题可以用动态规划来做,转移的思路比较简单,但是要进行相当多的处理。
    • 下面的讨论中,将用积分代表幸福感, i i i 人代表内向的人, e e e 人代表外向的人。
    • 首先要理解的是要求得积分,只需要统计总体积分即可,不需要实时更新网格中角色的积分。
    • 每一个网格均有三种情况 0 0 0 代表网格无人, 1 1 1 代表放置 i i i 人, 2 2 2 代表放置 e e e 人。
  • 由网格左上逐一向右下放置人员,假设此时将于 ( i , j ) (i,j) (i,j) 中放置人员,则可得知以下几点:
    1. i i i 行( 0 ∼ i − 1 0\sim i-1 0i1 行)已经放置好人员。
    2. ( i , j ) (i,j) (i,j) 放置人员只会影响到 ( i − 1 , j ) (i-1, j) (i1,j) ( i , j − 1 ) (i, j-1) (i,j1),即只会影响左邻居和上邻居,但因为后续人员未有放置,因此此时不需要考虑对右邻居和下邻居的影响。
  • 由前面第 2 点可以知道:
    • 当我们要在第 i i i 行放置人员并需要更新积分时,只需要考虑 i i i 行产生的行内积分,以及 i i i 行和 i − 1 i-1 i1 行之间产生的行间积分。【状态转移】
    • 因此我们只需要利用动态规划思想,往递归函数中传递当前行数、剩余 i i i 人数目、剩余 e e e 人数目。
    • 因为 n ≤ 5 n\leq 5 n5,所以单独某一行的所有可能情况数为 3 n 3^n 3n,用变量 m a s k ∈ [ 0 , 3 n − 1 ] mask\in [0,3^n-1] mask[0,3n1] 代表当前行的情况,我们只需要遍历 m m m 行的所有符合条件的 m a s k mask mask 即可更新得到最大的总体积分。【停止条件之一:遍历完 m m m 行】
  • 所谓的符合条件的 m a s k mask mask 指的就是在当前递归中, m a s k mask mask 所代表的当行放置情况中的 i i i 人数目和 e e e 人数目,分别不能超过当前剩余 i i i 人数目当前剩余 e e e 人数目。【剪枝】【停止条件之二:剩余 i i i 人数目和剩余 e e e 人数目均为 0 0 0
  • 因为 m m m n n n 的范围较小,因此可以通过提前计算得到不同 m a s k mask mask 的行内积分它们之间的行间积分
  • 最后总结以下:
    1. 该题考察了动态规划(记忆化搜索)、位运算(三进制计算确定当前行的状态掩码 m a s k mask mask 并用掩码 m a s k mask mask 进行行内积分与行间积分的计算),即状态压缩 dp 题目。
    2. 该题的动态规划转移思路不难,但是难于考虑到用状态压缩思路进行一系列的预处理。
    3. 该题的较为简单解法就是前面讨论的三进制逐行枚举,而官方解答中的方法二则是逐格枚举(基于轮廓线的动态规划),方法二细节更多,懒得看了,以后再做这题的时候再看看。

代码实现:

  • 代码参考自用户 zerotrac 的题解(参考内容二),加了一些注释:
    class Solution {
    private:
        int masks[243][5]; // (masks,3^5*5) n<=5,masks代表单行所有可能情况的集合,其预处理是为了快速取出集合中单个mask对应位置上的值
        int in[243], en[243], s[243], os[243][243]; //mask对应的(in i人数,en e人数,s 行内分数,os 行间分数)
        int dp[243][5][7][7]; // dp[上一行mask][当前行][剩余i人][剩余e人]
        int help[3][3] = {{0,0,0},{0,-60,-10},{0,-10,40}}; // 邻居间的积分更新,如help[1][2]表示i人和e人相邻的积分更新
    public:
        int getMaxGridHappiness(int m, int n, int ic, int ec) {
            // 1 预处理所有数据
            int n3 = pow(3, n); // 本次调用中mask的最大可能情况
            for(int mask = 0; mask < n3; ++mask) {
                // 1.1 处理masks数组
                for(int tmp = mask, i = 0; i < n; ++i, tmp /= 3)
                    masks[mask][i] = tmp % 3; // mask对应的第i位数值究竟是多少(0、1、2)
                // 1.2 处理in、en、s数组
                in[mask] = en[mask] = s[mask] = 0; // 初始化值为0
                for(int i = 0; i < n; ++i) {
                    if(masks[mask][i] == 0) continue; // 第i位没有人,则没有分数影响,不需要更新
                    if(masks[mask][i] == 1) ++in[mask], s[mask] += 120; // 第i位是i人,先更新分数,后续再更新对其他位置的影响
                    else if(masks[mask][i] == 2) ++en[mask], s[mask] += 40; // 第i位是e人
                    if(i > 0) // 计算行内人中相互之间的影响,只需要将第i个人和第i-1个人比较更新即可
                        s[mask] += help[masks[mask][i-1]][masks[mask][i]];
                }
            }
            // 1.3 处理os数组
            for(int m0 = 0; m0 < n3; ++m0) for(int m1 = 0; m1 < n3; ++m1) {
                os[m0][m1] = 0; // 初始化值为0
                for(int i = 0; i < n; ++i) os[m0][m1] += help[masks[m0][i]][masks[m1][i]];
            }
    
            // 2 动态规划
            memset(dp,-1,sizeof(dp)); // 初始化dp数组
            // pmask表示上一行mask,row表示当前行数,ic表示剩余i人数,ec表示剩余e人数
            function<int(int, int, int, int)> dfs = [&](int pmask, int row, int ic, int ec) ->int {
                if(row == m or ic+ec == 0) return 0; // 停止条件:处理完网格或者无人可放置
                if(dp[pmask][row][ic][ec] != -1) return dp[pmask][row][ic][ec];
                int score = 0;
                for(int mask = 0; mask < n3; ++mask) {
                    if(ic < in[mask] or ec < en[mask]) continue; // 如果当前mask的人数不足,则跳过即可
                    score = max(score, s[mask]+os[mask][pmask]+dfs(mask, row+1, ic-in[mask], ec-en[mask]));
                }
                return dp[pmask][row][ic][ec] = score;
            };
            return dfs(0, 0, ic, ec);
        }
    };
    

参考内容:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值