![image]()
package org.example;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取测试用例的组数 T
int T = sc.nextInt();
// 循环处理每一组测试用例
for (int t = 0; t < T; t++) {
// 读取矩阵的 行数N 和 列数M
int N = sc.nextInt();
int M = sc.nextInt();
// 定义二维数组 grid 存储整个数字矩阵
int[][] grid = new int[N][M];
// 双层循环:读取矩阵中每一个数字
for (int row = 0; row < N; row++) {
for (int col = 0; col < M; col++) {
grid[row][col] = sc.nextInt();
}
}
// ===================== 核心步骤1:生成一行的所有合法选法 =====================
// 题目规则:同一行不能选相邻的数字 → 二进制状态不能有连续的1
// validStates:存储所有【一行内合法】的选法(用二进制数字表示,1=选,0=不选)
List<Integer> validStates = new ArrayList<>();
// 遍历所有可能的选法:M列总共有 2^M 种选法(1<<M 等价于 2^M)
//意思是当前选法例如110,和左移一位100相与运算,如果有连续1结果就不为0
for (int state = 0; state < (1 << M); state++) {
// 位运算判断:无连续1 → 合法状态
// (state & (state << 1)) == 0 是判断二进制无连续1的固定公式
if ((state & (state << 1)) == 0) {
validStates.add(state);
}
}
// ===================== 核心步骤2:预处理每种选法的分数和 =====================
// sum[row][state]:第row行,用state这个选法,能拿到的数字总和
// 提前算好,后面DP直接用,不用重复计算
int[][] sum = new int[N][1 << M];
// 遍历每一行
for (int row = 0; row < N; row++) {
// 遍历这一行的所有合法选法
for (int state : validStates) {
int total = 0;
// 遍历每一列,判断是否被选中
for (int col = 0; col < M; col++) {
// 判断:当前选法是否选中了第col列
// (1 << col) 是列的标记,&运算结果≠0 代表选中
if ((state & (1 << col)) != 0) {
// 选中了,就把这个数字加入总分
total += grid[row][col];
}
}
// 把当前行、当前选法的总分存起来
sum[row][state] = total;
}
}
// ===================== 核心步骤3:定义DP数组 + 初始化 =====================
// DP数组定义:dp[row][state] = 处理到第row行,且第row行选state状态时,最大总分
// 二维数组原因:必须记录【行号】+【当前行选法】,才能判断和上一行是否冲突
int[][] dp = new int[N][1 << M];
// 初始化第一行:第一行没有上一行,直接等于自身选法的总分
for (int state : validStates) {
dp[0][state] = sum[0][state];
}
// ===================== 核心步骤4:DP状态转移(逐行计算最大总分) =====================
// 从第二行开始遍历(第一行已初始化)
for (int row = 1; row < N; row++) {
// 枚举:当前行的所有合法选法 curr
for (int curr : validStates) {
// 枚举:上一行的所有合法选法 prev
for (int prev : validStates) {
// ===================== 关键:判断两行选法是否8邻域冲突 =====================
// 题目规则:不能8邻域相邻 → 3个条件必须同时满足
// 1. prev & curr == 0:上下正对着的列不能同时选
// 2. prev & (curr << 1) == 0:对角线(右上)不能同时选
// 3. prev & (curr >> 1) == 0:对角线(左上)不能同时选
boolean noConflict = (prev & curr) == 0
&& (prev & (curr << 1)) == 0
&& (prev & (curr >> 1)) == 0;
// 如果两行选法不冲突,就可以转移状态
if (noConflict) {
// 状态转移公式:
// 当前行最大总分 = max(自身原值, 上一行最大总分 + 当前行选法的分数)
dp[row][curr] = Math.max(dp[row][curr], dp[row-1][prev] + sum[row][curr]);
}
}
}
}
// ===================== 核心步骤5:取最终答案 =====================
// 答案:最后一行所有合法选法中的最大总分(所有行处理完毕)
int ans = 0;
for (int state : validStates) {
ans = Math.max(ans, dp[N-1][state]);
}
// 输出当前测试用例的答案
System.out.println(ans);
}
sc.close();
}
}