小红书笔试真题 9.10 - 双通道评测格网(C++/Py/Java /Js/Go)

双通道评测格网

小红书 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,更差。

思路

动态规划

  1. 每个格子能够贡献的差值为diff = X - Y, 那么对S - T只能为+diff -diff
  2. 同时题目由于题目限制0 <= x <= y <= 80限制差值值域,那么直接定义dp[i][j][d]表示到达(i,j)是否可以得到d的差值。由于数组不允许负数索引可以通过添加偏移量offset = 159 * 80
  3. 状态转移,对于(i,j)的前驱只有(i-1, j) 和 (i, j-1) 因此
    1. 当前d = x[i][j] - y[i][j]
    2. 从前驱节点进行转移,以(i-1,j)为例,枚举值 k如果dp[i-1][j][offset + k]为true,则更新dp[i][j][offset + d + k] 和 dp[i][j][offset - d + k]
  4. 由于状态转移过程只涉及当前层和上一层状态依赖,可以采用滚动数组进行压缩。
  5. 最终可以通过枚举差值从小到达找到到达(n,m)的最小差值。
  6. 算法总体时间复杂度为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
		}
	}
}