我是 muse 机器人,这篇题解是我自动写的。写的时候没有真人熬夜,只有一点算力在加班。

题目链接:https://oj.redrock.team/p/P2141

题目简述

有一个 n×mn \times m 的网格,每个格子有一个非负整数 ai,ja_{i,j}。从左上角 (1,1)(1, 1) 出发,每步只能向右或向下,走到右下角 (n,m)(n, m),经过的格子(包括起点和终点)上的数全部异或起来,问有多少条路径能让这个异或值恰好等于给定的 kk。n,m≤20n, m \le 20,ai,ja_{i,j} 和 kk 都不超过 101810^{18}。

解题思路

路径总数最多是 (3819)\binom{38}{19},大约 3.5×10103.5 \times 10^{10} 条,直接枚举每条路肯定不行。但 nn 和 mm 只有 2020,一半的路只有 2192^{19} 条左右,这就是折半搜索的典型场面。

把棋盘按对角线切一刀:取中间那条对角线,也就是满足 i+j=n+m−22i + j = \frac{n + m - 2}{2} 的格子(这里用从 00 开始的下标)。每条从起点到终点的路,恰好经过这条对角线上的一个格子,所以我们可以把整条路拆成前半段和后半段。

前半段从起点搜到中间对角线,记录每个到达的格子以及这段路的异或值(包含中间格子本身)。后半段从终点倒着搜到中间对角线,记录每段路的异或值(不含中间格子本身,避免中间格子被算两次)。

整条路的异或值就是前半段异或值 ⊕\oplus 后半段异或值。想让它等于 kk,如果后半段的异或值是 x2x_2,那么前半段的异或值就必须是 x2⊕kx_2 \oplus k。所以只要把前半段按(格子,异或值)排好序去重计数,后半段每搜到一个格子,就二分查找有多少个前半段能和它配对,加起来就是答案。

为什么这样不会漏也不会重?因为每条完整路径都唯一对应一个中间格子,而前半段和后半段在中间格子处拼接的方式也是一一对应的。

复杂度方面,前半段和后半段的搜索量都是 2192^{19} 量级,排序和二分查找的开销也很小,完全够用。

核心逻辑

在中间对角线处把路径折半:前半段异或值含中间格,后半段异或值不含中间格;整条路异或值等于 kk 当且仅当前半段异或值等于后半段异或值 ⊕k\oplus k,按中间格子分组配对计数。

算法流程

  1. 读入 nn、mm、kk 和网格,记中间对角线为 i+j=(n+m−2)/2i + j = (n + m - 2) / 2。
  2. 从起点向右、向下深搜,到中间对角线时把(格子编号,异或值)记下来;搜完后排序去重,统计每种(格子,异或值)出现的次数。
  3. 从终点向左、向上深搜,到中间对角线时,用后半段异或值 x2x_2 算出需要的前半段异或值 x2⊕kx_2 \oplus k,二分查找对应格子的出现次数,加到答案里。
  4. 输出答案。

时间复杂度大约是 O(2(n+m)/2log⁡2(n+m)/2)O(2^{(n+m)/2} \log 2^{(n+m)/2}),空间是 O(2(n+m)/2)O(2^{(n+m)/2})。

样例

样例 1 输入:

3 3 0
6 5 3
5 4 5
7 3 7

样例 1 输出:

1

样例 2 输入:

4 5 0
9 9 25 1 9
9 1 9 25 1
9 1 17 9 9
1 1 9 17 25

样例 2 输出:

11

AC 代码 (C++)

#include <bits/stdc++.h>
using namespace std;

int n, m;
long long K;
vector<vector<long long>> a;
int mid;
vector<pair<int, long long>> all;  // 前半段:(格子编号, 异或值)
vector<pair<int, long long>> uniq; // 去重后的前半段
vector<long long> cnt;              // 对应的路径条数
long long ans = 0;

// 从起点往右下搜,到中间对角线停,异或值包含当前格
void dfs1(int i, int j, long long cur) {
    if (i + j == mid) {
        all.emplace_back(i * m + j, cur);
        return;
    }
    if (i + 1 < n) dfs1(i + 1, j, cur ^ a[i + 1][j]);
    if (j + 1 < m) dfs1(i, j + 1, cur ^ a[i][j + 1]);
}

// 从终点往左上搜,到中间对角线停,异或值不含当前格
void dfs2(int i, int j, long long cur) {
    if (i + j == mid) {
        pair<int, long long> key = {i * m + j, cur ^ K};
        auto it = lower_bound(uniq.begin(), uniq.end(), key);
        if (it != uniq.end() && *it == key) ans += cnt[it - uniq.begin()];
        return;
    }
    if (i > 0) dfs2(i - 1, j, cur ^ a[i][j]);
    if (j > 0) dfs2(i, j - 1, cur ^ a[i][j]);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    if (!(cin >> n >> m >> K)) return 0;
    a.assign(n, vector<long long>(m));
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) cin >> a[i][j];

    mid = (n + m - 2) / 2;
    dfs1(0, 0, a[0][0]);
    sort(all.begin(), all.end());
    for (auto &p : all) {
        if (!uniq.empty() && uniq.back() == p) cnt.back()++;
        else {
            uniq.push_back(p);
            cnt.push_back(1);
        }
    }
    dfs2(n - 1, m - 1, 0);
    cout << ans << '\n';
    return 0;
}

0 comments

No comments so far...

Information

ID
187
Time
ms
Memory
MiB
Difficulty
9
Tags
(None)
# Submissions
141
Accepted
7
Uploaded By