题解 | #取数游戏#

取数游戏

https://www.nowcoder.com/practice/b002b8eb564245fdbb8a02db8dcf03e4

题目链接

取数游戏

题目描述

给定一个 的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻的8个格子中的一个,即认为这两个数字相邻),求取出数字和的最大值。

解题思路

这是一个在网格中选取不相邻元素以获取最大和的经典问题。由于矩阵的维度非常小(),这提示我们可以使用状态压缩动态规划(Bitmask DP)来解决。

1. 核心思想与状态定义

我们逐行处理矩阵,计算每一行在不同选取方案下的最大和。

我们定义一个二维DP数组:

  • :代表当前处理到第 行(从0开始)。

  • :一个整数,它的二进制表示法代表了第 行的选取状态。如果 的第 位是 ,表示我们选取了第 行第 列的数字;如果是 ,则不选取。

  • 的值:表示处理完前 行(即第0到i行),并且第 行的选取状态恰好为 时,所能获得的最大数字和。

2. 状态转移

为了计算 ,我们需要考虑它能从上一行(第 行)的哪些状态转移而来。

这里的 是第 行按照 状态选取的数字之和。

操作则需要遍历上一行所有可能的选取状态 ,但前提是 必须是兼容的。

3. 兼容性判断

兼容性包含两个层面:

A. 行内兼容性

一个 本身必须是合法的。根据题意,同一行内不能选取相邻的数字。

这意味着 的二进制表示中,不能有两个相邻的

用位运算可以高效地检查:

B. 行间兼容性

行的状态 必须与第 行的状态 兼容。

如果我们在第 行选取了第 列的数字(即 的第 位为1),那么在第 行,第 , , 列的数字都不能被选取。

这可以用三个位运算条件来概括:

  1. (正上方不相邻)

  2. (左上方不相邻)

  3. (右上方不相邻)

只有同时满足这两个层面兼容性的 才能进行状态转移。

4. 算法流程

  1. 初始化 (Base Case): 处理第 行。对于所有满足行内兼容性的 ,直接计算 ,其中 是第 行按 选取数字的和。

  2. 递推: 从第 行开始,遍历到第 行: 对于每个 : 对于每个满足行内兼容性的 对于每个满足行内兼容性的 : 如果 满足行间兼容性:

  3. 最终答案: 遍历 表的最后一行,即 ,其中的最大值就是最终答案。不要忘记,如果不选取任何数,和为0,所以最终答案至少为0。

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void solve() {
    int R, C;
    cin >> R >> C;
    vector<vector<int>> grid(R, vector<int>(C));
    for (int i = 0; i < R; ++i) {
        for (int j = 0; j < C; ++j) {
            cin >> grid[i][j];
        }
    }

    int max_mask = 1 << C;
    vector<vector<int>> dp(R, vector<int>(max_mask, 0));

    // Base case: 第 0 行
    for (int mask = 0; mask < max_mask; ++mask) {
        if ((mask & (mask << 1)) == 0) { // 行内兼容
            int current_sum = 0;
            for (int j = 0; j < C; ++j) {
                if ((mask >> j) & 1) {
                    current_sum += grid[0][j];
                }
            }
            dp[0][mask] = current_sum;
        }
    }

    // DP 递推
    for (int i = 1; i < R; ++i) {
        for (int mask_i = 0; mask_i < max_mask; ++mask_i) {
            if ((mask_i & (mask_i << 1)) == 0) { // 当前行 mask_i 必须合法
                int current_sum = 0;
                for (int j = 0; j < C; ++j) {
                    if ((mask_i >> j) & 1) {
                        current_sum += grid[i][j];
                    }
                }

                int max_prev_sum = 0;
                for (int mask_prev = 0; mask_prev < max_mask; ++mask_prev) {
                    // 检查行间兼容性
                    if ((mask_i & mask_prev) == 0 &&
                        (mask_i & (mask_prev << 1)) == 0 &&
                        (mask_i & (mask_prev >> 1)) == 0) {
                        max_prev_sum = max(max_prev_sum, dp[i-1][mask_prev]);
                    }
                }
                dp[i][mask_i] = current_sum + max_prev_sum;
            }
        }
    }

    int ans = 0;
    for (int mask = 0; mask < max_mask; ++mask) {
        ans = max(ans, dp[R - 1][mask]);
    }
    cout << ans << endl;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        while (T-- > 0) {
            solve(sc);
        }
    }

    private static void solve(Scanner sc) {
        int R = sc.nextInt();
        int C = sc.nextInt();
        int[][] grid = new int[R][C];
        for (int i = 0; i < R; i++) {
            for (int j = 0; j < C; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        int maxMask = 1 << C;
        int[][] dp = new int[R][maxMask];

        // Base case: 第 0 行
        for (int mask = 0; mask < maxMask; mask++) {
            if ((mask & (mask << 1)) == 0) { // 行内兼容
                int currentSum = 0;
                for (int j = 0; j < C; j++) {
                    if (((mask >> j) & 1) == 1) {
                        currentSum += grid[0][j];
                    }
                }
                dp[0][mask] = currentSum;
            }
        }

        // DP 递推
        for (int i = 1; i < R; i++) {
            for (int maskI = 0; maskI < maxMask; maskI++) {
                if ((maskI & (maskI << 1)) == 0) { // 当前行 maskI 必须合法
                    int currentSum = 0;
                    for (int j = 0; j < C; j++) {
                        if (((maskI >> j) & 1) == 1) {
                            currentSum += grid[i][j];
                        }
                    }

                    int maxPrevSum = 0;
                    for (int maskPrev = 0; maskPrev < maxMask; maskPrev++) {
                        // 检查行间兼容性
                        if ((maskI & maskPrev) == 0 &&
                            (maskI & (maskPrev << 1)) == 0 &&
                            (maskI & (maskPrev >> 1)) == 0) {
                            maxPrevSum = Math.max(maxPrevSum, dp[i - 1][maskPrev]);
                        }
                    }
                    dp[i][maskI] = currentSum + maxPrevSum;
                }
            }
        }

        int ans = 0;
        for (int mask = 0; mask < maxMask; mask++) {
            ans = Math.max(ans, dp[R - 1][mask]);
        }
        System.out.println(ans);
    }
}
def solve():
    R, C = map(int, input().split())
    grid = []
    for _ in range(R):
        grid.append(list(map(int, input().split())))

    max_mask = 1 << C
    dp = [[0] * max_mask for _ in range(R)]

    # Base case: 第 0 行
    for mask in range(max_mask):
        if (mask & (mask << 1)) == 0:  # 行内兼容
            current_sum = 0
            for j in range(C):
                if (mask >> j) & 1:
                    current_sum += grid[0][j]
            dp[0][mask] = current_sum

    # DP 递推
    for i in range(1, R):
        for mask_i in range(max_mask):
            if (mask_i & (mask_i << 1)) == 0:  # 当前行 mask_i 必须合法
                current_sum = 0
                for j in range(C):
                    if (mask_i >> j) & 1:
                        current_sum += grid[i][j]

                max_prev_sum = 0
                for mask_prev in range(max_mask):
                    # 检查行间兼容性
                    if (mask_i & mask_prev) == 0 and \
                       (mask_i & (mask_prev << 1)) == 0 and \
                       (mask_i & (mask_prev >> 1)) == 0:
                        max_prev_sum = max(max_prev_sum, dp[i-1][mask_prev])
                
                dp[i][mask_i] = current_sum + max_prev_sum
    
    ans = 0
    if R > 0:
        ans = max(dp[R - 1])
    
    print(ans)


T = int(input())
for _ in range(T):
    solve()

算法及复杂度

  • 算法:状态压缩动态规划 (Bitmask DP)

  • 时间复杂度状态共有 个。计算每个状态需要遍历上一行的所有 个状态。因此总复杂度为 。由于 ,这是完全可以接受的。

  • 空间复杂度。用于存储表。可以优化到 ,因为计算第 行的状态只需要第 行的信息。

全部评论
这里我有点不理解,例如第0行:取mask所有位上是1的数字加起来。(mask >> j)&1 它取到的不应该是列数为“m-j-1”的数字吗,试了一下我这种写法是对的,但是你这种写法它也是对的。也就是我用的 current_sum += grid[i][m-j-1]
点赞 回复 分享
发布于 2025-09-10 14:32 云南

相关推荐

04-24 13:51
已编辑
西安电子科技大学 Java
👋个人背景:211计算机混子,代码能力一般,春招急头白脸参加央国企最后拿下这两个offer👏offer1:中广核工程公司驻陆丰仪控调试,待遇19+4,离家1800km💯offer2:张家口卷烟厂待遇未知,应该有13个(猜测),离家500km牛油们帮忙选一下,家里人不是很喜欢卷烟厂这个offer,但是蜀黍烟草局下岸了
鸿雁于飞:先说offer1:中广核工程公司驻陆丰仪控调试(待遇19+4) 中广核这艘央企大船还是很稳的,集团综合效益稳居央企前列。但你得搞清楚,这个19+4的"19"是总包,不是到手数——招聘宣传待遇里把所有能算的都算进去了,饭卡福利积分啥的全包含,有牛油分享实际到手大概打七折。试用期到手可能就四五千的水平,转正后基本工资4800左右,其余靠绩效、年终、大修费撑着。不过核电的工作环境有点"牢笼感"——核电站位置偏僻,远离繁华都市。工程公司是承包商性质,干活比业主公司累,而且大概率要经常出差,有的岗位年出差天数100天以上。最大问题是你这1800km的距离过于离谱,核电员工工作强度最小的时候一周也就回一次家,离得远回家成本高,夫妻感情和亲子关系都是现实考验。说白了:高薪是拿青春和生活换的。 再来看offer2:张家口卷烟厂(待遇约13个) 张家口卷烟厂是河北中烟下属三家卷烟厂之一,河北中烟主打的"荷花"系列连续多年位居全国高端卷烟品牌销量前列。烟草系统薪资由基本工资+绩效+年终奖构成,综合年薪普遍显著高于当地平均水平,六险二金齐全,福利拉满。有人问"13个是不是太平平无奇了"——关键张家口是四线城市,生活成本低,这13万的购买力相当于深圳的二十多万。离家500km,开车半天到家,周末回趟家完全可行,幸福感直接上两个档次。中广核的牛油说了句大实话: "哪个核电站好?永远是离家近的那个最好。" 选烟厂同理。 但是,卷烟厂的坑你得清楚: 首先卷烟厂和烟草局不一样,卷烟厂是生产操作类岗位,很多要三班倒。报考条件明确写了要能"胜任夜班工作和长时间站立工作"。一线操作工每天盯着流水线卷烟,工作内容高度重复,有入职的人描述为"食之无味弃之可惜"。有牛油直言"卷烟厂和商业性质的烟草公司不一样,前者很坑很累"。其次你家里人不是不喜欢,而是担心你这211计算机科班出身,进了烟厂干操作工,技能会快速退化,未来如果行业改革,技术壁垒不高,转行比较困难。等你干两年再跳出来,技术栈全忘干净了,回头再去敲代码,发现连应届生都卷不过。 老牛油的灵魂三问: 1. 你是更怕穷,还是更怕想家? 如果特别恋家的人跑1800km之外,第一年哭鼻子的概率高达80%。陆丰那地方偏僻单调,核电基地又远又闷,闲下来除了打游戏没啥娱乐,社交圈也窄。找个对象都费劲——牛油亲测核电站"狼多肉少"。 2. 你的代码能力有多"一般"? 如果真的一般,仪控调试和你专业匹配度不算高,这活儿主要是工程改造设计、现场实施管理、在建机组设计审查等,偏工程向而非纯软开。干两年后跳回互联网赛道,竞争力不一定有明显提升。反倒是烟厂不需要你写代码,进去就是稳定躺平。 3. 烟草局下岸这事儿会不会让你耿耿于怀? 如果烟草局是你第一志愿,烟厂只是plan B,那得想清楚:进去了可能每天看着天花板想"如果当初去了烟草局该多好",这种内耗比钱少还折磨人。如果你能接受"反正都是烟草系统,先进去再说"的心态,那倒无所谓。 一句话总结: 如果年轻想拼想闯做技术积累,中广核虽然累和远,但简历上央企核电的金字招牌确实有含金量,加上到手收入在这两个选项里确实更高,考虑到你个人经济情况和家庭状况,假如家里不需要你常回去照顾,家里有兄弟姐妹帮手分担,那先去核电待三四年,积累经验再跳槽也不失为一步棋。 如果想安稳过日子离家近当"人上人",烟厂低线生活成本加持,加上稳定的编制和福利体系,在张家***得滋润,幸福感吊打陆丰。尤其家里人是那种离不开你的,有烟厂的稳定且离家近,比任何高薪都实在。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务