双通道评测格网
小红书 9月10号 笔试真题 第二题
题目描述
给定一张 NNN 行 MMM 列的评测格网。格子 (i,j)(i,j)(i,j) 上有两个通道分 Xi,jX_{i,j}Xi,j 与 Yi,jY_{i,j}Yi,j。
从 (1,1)(1,1)(1,1) 走到 (N,M)(N,M)(N,M),每次只能向右走到 (i,j+1)(i,j+1)(i,j+1) 或向下走到 (i+1,j)(i+1,j)(i+1,j),不可越界。
路径上每个格子(含起终点)须把 Xi,jX_{i,j}Xi,j 与 Yi,jY_{i,j}Yi,j 分别记入主账户与对照账户,二者各得其一。
设主账户合计为 SSS,对照账户合计为 TTT。求所有合法路径与分配方案下 ∣S−T∣|S-T|∣S−T∣ 的最小值。
输入描述
输入共 2N+12N+12N+1 行。
第一行两个整数 NNN 和 MMM。
接下来 NNN 行,每行 MMM 个整数,表示 Xi,jX_{i,j}Xi,j。
再接下来 NNN 行,每行 MMM 个整数,表示 Yi,jY_{i,j}Yi,j。
1≤N,M≤801\le N,M\le 801≤N,M≤80,0≤Xi,j,Yi,j≤800\le X_{i,j},Y_{i,j}\le 800≤Xi,j,Yi,j≤80。
输出描述
输出一行一个整数,即最小的 ∣S−T∣|S-T|∣S−T∣。
样例 1
输入
2 2
4 1
2 5
2 3
6 1
输出
0
说明
路径 (1,1)→(1,2)→(2,2)(1,1)\to(1,2)\to(2,2)(1,1)→(1,2)→(2,2)。
(1,1)(1,1)(1,1):主账户记 222,对照账户记 444。
(1,2)(1,2)(1,2):主账户记 111,对照账户记 333。
(2,2)(2,2)(2,2):主账户记 555,对照账户记 111。
此时 S=8S=8S=8,T=8T=8T=8,∣S−T∣=0|S-T|=0∣S−T∣=0。
另一条路径 (1,1)→(2,1)→(2,2)(1,1)\to(2,1)\to(2,2)(1,1)→(2,1)→(2,2) 上,∣S−T∣|S-T|∣S−T∣ 至少为 222,更差。
思路
动态规划
- 每个格子能够贡献的差值为
diff = X - Y, 那么对S - T只能为+diff -diff - 同时题目由于题目限制
0 <= x <= y <= 80限制差值值域,那么直接定义dp[i][j][d]表示到达(i,j)是否可以得到d的差值。由于数组不允许负数索引可以通过添加偏移量offset = 159 * 80 - 状态转移,对于(i,j)的前驱只有
(i-1, j) 和 (i, j-1)因此- 当前
d = x[i][j] - y[i][j] - 从前驱节点进行转移,以(i-1,j)为例,枚举值 k如果
dp[i-1][j][offset + k]为true,则更新dp[i][j][offset + d + k]和dp[i][j][offset - d + k]
- 当前
- 由于状态转移过程只涉及当前层和上一层状态依赖,可以采用滚动数组进行压缩。
- 最终可以通过枚举差值从小到达找到到达
(n,m)的最小差值。 - 算法总体时间复杂度为
O(n*m*(n+m)*80)
C++
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXD = 12720;
const int SIZE = MAXD * 2 + 1;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, M;
cin >> N >> M;
vector<vector<int>> X(N, vector<int>(M));
vector<vector<int>> Y(N, vector<int>(M));
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> X[i][j];
}
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> Y[i][j];
}
}
// dp[j][k] 表示当前处理到第 j 列时,差值 k - MAXD 是否可以达到
vector<vector<bool>> dp(M, vector<bool>(SIZE, false));
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
int d = abs(X[i][j] - Y[i][j]);
// 起点
if (i == 0 && j == 0) {
dp[j][MAXD + d] = true;
dp[j][MAXD - d] = true;
continue;
}
vector<bool> cur(SIZE, false);
// 前驱最多经过 i + j 个格子
int maxPrev = (i + j) * 80;
// 从上方转移
if (i > 0) {
for (int k = -maxPrev; k <= maxPrev; k++) {
int idx = MAXD + k;
if (!dp[j][idx]) {
continue;
}
cur[idx + d] = true;
cur[idx - d] = true;
}
}
// 从左边转移
if (j > 0) {
for (int k = -maxPrev; k <= maxPrev; k++) {
int idx = MAXD + k;
if (!dp[j - 1][idx]) {
continue;
}
cur[idx + d] = true;
cur[idx - d] = true;
}
}
dp[j] = move(cur);
}
}
// 找到绝对值最小的可达差值
for (int ans = 0; ans <= MAXD; ans++) {
if (dp[M - 1][MAXD + ans] ||
dp[M - 1][MAXD - ans]) {
cout << ans << '\n';
return 0;
}
}
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
static final int MAXD = 12720;
static final int SIZE = MAXD * 2 + 1;
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
int[][] X = new int[N][M];
int[][] Y = new int[N][M];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
X[i][j] = sc.nextInt();
}
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
Y[i][j] = sc.nextInt();
}
}
// dp[j][k] 表示当前处理到第 j 列时,差值 k - MAXD 是否可以达到
boolean[][] dp = new boolean[M][SIZE];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
int d = Math.abs(X[i][j] - Y[i][j]);
// 起点
if (i == 0 && j == 0) {
dp[j][MAXD + d] = true;
dp[j][MAXD - d] = true;
continue;
}
boolean[] cur = new boolean[SIZE];
// 前驱最多经过 i + j 个格子
int maxPrev = (i + j) * 80;
// 从上方转移
if (i > 0) {
for (int k = -maxPrev; k <= maxPrev; k++) {
int idx = MAXD + k;
if (!dp[j][idx]) {
continue;
}
cur[idx + d] = true;
cur[idx - d] = true;
}
}
// 从左边转移
if (j > 0) {
for (int k = -maxPrev; k <= maxPrev; k++) {
int idx = MAXD + k;
if (!dp[j - 1][idx]) {
continue;
}
cur[idx + d] = true;
cur[idx - d] = true;
}
}
dp[j] = cur;
}
}
// 找到绝对值最小的可达差值
for (int ans = 0; ans <= MAXD; ans++) {
if (dp[M - 1][MAXD + ans] ||
dp[M - 1][MAXD - ans]) {
System.out.println(ans);
return;
}
}
}
}
python
python
import sys
MAXD = 12720
SIZE = MAXD * 2 + 1
data = list(map(int, sys.stdin.buffer.read().split()))
p = 0
N = data[p]
M = data[p + 1]
p += 2
X = [[0] * M for _ in range(N)]
Y = [[0] * M for _ in range(N)]
for i in range(N):
for j in range(M):
X[i][j] = data[p]
p += 1
for i in range(N):
for j in range(M):
Y[i][j] = data[p]
p += 1
# dp[j][k] 表示当前处理到第 j 列时,差值 k - MAXD 是否可以达到
dp = [[False] * SIZE for _ in range(M)]
for i in range(N):
for j in range(M):
d = abs(X[i][j] - Y[i][j])
# 起点
if i == 0 and j == 0:
dp[j][MAXD + d] = True
dp[j][MAXD - d] = True
continue
cur = [False] * SIZE
# 前驱最多经过 i + j 个格子
maxPrev = (i + j) * 80
# 从上方转移
if i > 0:
for k in range(-maxPrev, maxPrev + 1):
idx = MAXD + k
if not dp[j][idx]:
continue
cur[idx + d] = True
cur[idx - d] = True
# 从左边转移
if j > 0:
for k in range(-maxPrev, maxPrev + 1):
idx = MAXD + k
if not dp[j - 1][idx]:
continue
cur[idx + d] = True
cur[idx - d] = True
dp[j] = cur
# 找到绝对值最小的可达差值
for ans in range(MAXD + 1):
if dp[M - 1][MAXD + ans] or dp[M - 1][MAXD - ans]:
print(ans)
break
javascript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const lines = [];
rl.on('line', line => {
lines.push(line.trim());
});
rl.on('close', () => {
const data = lines.join(' ').split(/\s+/).map(Number);
let p = 0;
const MAXD = 12720;
const SIZE = MAXD * 2 + 1;
const N = data[p++];
const M = data[p++];
const X = Array.from({ length: N }, () => Array(M));
const Y = Array.from({ length: N }, () => Array(M));
for (let i = 0; i < N; i++) {
for (let j = 0; j < M; j++) {
X[i][j] = data[p++];
}
}
for (let i = 0; i < N; i++) {
for (let j = 0; j < M; j++) {
Y[i][j] = data[p++];
}
}
// dp[j][k] 表示当前处理到第 j 列时,差值 k - MAXD 是否可以达到
let dp = Array.from({ length: M }, () => new Array(SIZE).fill(false));
for (let i = 0; i < N; i++) {
for (let j = 0; j < M; j++) {
const d = Math.abs(X[i][j] - Y[i][j]);
// 起点
if (i === 0 && j === 0) {
dp[j][MAXD + d] = true;
dp[j][MAXD - d] = true;
continue;
}
const cur = new Array(SIZE).fill(false);
// 前驱最多经过 i + j 个格子
const maxPrev = (i + j) * 80;
// 从上方转移
if (i > 0) {
for (let k = -maxPrev; k <= maxPrev; k++) {
const idx = MAXD + k;
if (!dp[j][idx]) {
continue;
}
cur[idx + d] = true;
cur[idx - d] = true;
}
}
// 从左边转移
if (j > 0) {
for (let k = -maxPrev; k <= maxPrev; k++) {
const idx = MAXD + k;
if (!dp[j - 1][idx]) {
continue;
}
cur[idx + d] = true;
cur[idx - d] = true;
}
}
dp[j] = cur;
}
}
// 找到绝对值最小的可达差值
for (let ans = 0; ans <= MAXD; ans++) {
if (dp[M - 1][MAXD + ans] ||
dp[M - 1][MAXD - ans]) {
console.log(ans);
return;
}
}
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
const MAXD = 12720
const SIZE = MAXD*2 + 1
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var N, M int
fmt.Fscan(in, &N, &M)
X := make([][]int, N)
Y := make([][]int, N)
for i := 0; i < N; i++ {
X[i] = make([]int, M)
Y[i] = make([]int, M)
}
for i := 0; i < N; i++ {
for j := 0; j < M; j++ {
fmt.Fscan(in, &X[i][j])
}
}
for i := 0; i < N; i++ {
for j := 0; j < M; j++ {
fmt.Fscan(in, &Y[i][j])
}
}
// dp[j][k] 表示当前处理到第 j 列时,差值 k - MAXD 是否可以达到
dp := make([][]bool, M)
for j := 0; j < M; j++ {
dp[j] = make([]bool, SIZE)
}
for i := 0; i < N; i++ {
for j := 0; j < M; j++ {
d := X[i][j] - Y[i][j]
if d < 0 {
d = -d
}
// 起点
if i == 0 && j == 0 {
dp[j][MAXD+d] = true
dp[j][MAXD-d] = true
continue
}
cur := make([]bool, SIZE)
// 前驱最多经过 i + j 个格子
maxPrev := (i + j) * 80
// 从上方转移
if i > 0 {
for k := -maxPrev; k <= maxPrev; k++ {
idx := MAXD + k
if !dp[j][idx] {
continue
}
cur[idx+d] = true
cur[idx-d] = true
}
}
// 从左边转移
if j > 0 {
for k := -maxPrev; k <= maxPrev; k++ {
idx := MAXD + k
if !dp[j-1][idx] {
continue
}
cur[idx+d] = true
cur[idx-d] = true
}
}
dp[j] = cur
}
}
// 找到绝对值最小的可达差值
for ans := 0; ans <= MAXD; ans++ {
if dp[M-1][MAXD+ans] || dp[M-1][MAXD-ans] {
fmt.Fprintln(out, ans)
return
}
}
}