Chebyshev Cafe

Chebyshev Cafe

切比雪夫距离和哈密顿距离的转化,前缀和预处理

题意

给定一个 \(N \times N\) 的网格,每个格子 \((i,j)\) 有权重

\[w(i,j) = (A_i \times B_j) \bmod M \]

对于每个格子 \((i,j)\),定义

\[f(i,j)=\sum_{s=1}^{N}\sum_{t=1}^{N} w(s,t)\cdot \max(|s-i|,|t-j|) \]

求

\[\bigoplus_{i=1}^{N}\bigoplus_{j=1}^{N}\left(f(i,j)+(i-1)N+(j-1)\right) \]

其中 \(\oplus\) 表示按位异或。


核心转化

切比雪夫距离可以转化为两个一维绝对值之和:

\[\max(|x|,|y|)=\frac{|x+y|+|x-y|}{2} \]

令

\[x=s-i,\qquad y=t-j \]

则

\[x+y=(s+t)-(i+j) \]

\[x-y=(s-t)-(i-j) \]

因此

\[\max(|s-i|,|t-j|) = \frac{ |(s+t)-(i+j)|+|(s-t)-(i-j)| }{2} \]

代回 \(f(i,j)\):

\[f(i,j)=\frac{S_1(i+j)+S_2(i-j)}{2} \]

其中

\[S_1(x)=\sum_{s,t} w(s,t)\cdot |(s+t)-x| \]

\[S_2(y)=\sum_{s,t} w(s,t)\cdot |(s-t)-y| \]

也就是说,把所有格子分别投影到两条对角线方向:

  • 正对角线方向:看 \(s+t\) 与 \(i+j\) 的大小关系
  • 反对角线方向:看 \(s-t\) 与 \(i-j\) 的大小关系

对每个方向,分别计算一维绝对距离和,再相加除以 \(2\) 即可。


预处理

1. 统计正对角线权重

把所有格子按 \(s+t\) 分组,令

\[linest[k]=\sum_{s+t=k} w(s,t) \]

其中 \(2 \le k \le 2N\)。

2. 统计反对角线权重

把所有格子按 \(s-t\) 分组,为了数组下标非负,平移 \(N\):

\[line\_st[k]=\sum_{s-t+N=k} w(s,t) \]

其中 \(1 \le k \le 2N-1\)。

3. 前缀和

对于一维数组 \(W[k]\),要快速计算

\[\sum_k W[k]\cdot |k-x| \]

拆成左右两段:

  • 当 \(k < x\) 时,\(|k-x| = x-k\),贡献为 \(x\cdot W[k] - k\cdot W[k]\)
  • 当 \(k > x\) 时,\(|k-x| = k-x\),贡献为 \(k\cdot W[k] - x\cdot W[k]\)
  • 当 \(k = x\) 时,贡献为 \(0\),直接跳过

所以需要维护两个前缀和:

  • 权重前缀和:\(preW[k]=\sum_{j\le k} W[j]\)
  • 加权前缀和:\(preD[k]=\sum_{j\le k} j\cdot W[j]\)

这样,对于目标值 \(x\):

\[\sum_k W[k]\cdot|k-x| = \underbrace{x\cdot preW[x-1] - preD[x-1]}_{\text{左侧 } k<x} + \underbrace{(preD[max]-preD[x]) - x\cdot(preW[max]-preW[x])}_{\text{右侧 } k>x} \]


查询每个格子

对于格子 \((i,j)\):

  • 正对角线目标值:\(x=i+j\)
  • 反对角线目标值:\(y=i-j+N\)

计算 \(S_1(x)\)

左侧(\(s+t < x\)):

\[left_1 = x\cdot preW1[x-1] - preD1[x-1] \]

右侧(\(s+t > x\)):

\[right_1 = (preD1[2N]-preD1[x]) - x\cdot(preW1[2N]-preW1[x]) \]

\[S_1 = left_1 + right_1 \]

计算 \(S_2(y)\)

左侧(\(s-t+N < y\)):

\[left_2 = y\cdot preW2[y-1] - preD2[y-1] \]

右侧(\(s-t+N > y\)):

\[right_2 = (preD2[2N-1]-preD2[y]) - y\cdot(preW2[2N-1]-preW2[y]) \]

\[S_2 = left_2 + right_2 \]

合并

\[f(i,j)=\frac{S_1+S_2}{2} \]

最后对每个格子计算

\[val(i,j)=f(i,j)+(i-1)N+(j-1) \]

把所有 \(val(i,j)\) 按位异或起来即可。


为什么这样拆是对的

对于一维情况,要算 \(\sum_k W[k]\cdot |k-x|\):

  • 所有满足 \(k<x\) 的项,\(k-x\) 是负数,去掉绝对值后变成 \(x-k\),累加就是 \(x\cdot\sum W[k] - \sum k\cdot W[k]\),正好用前缀和一次算出。
  • 所有满足 \(k>x\) 的项,\(k-x\) 是正数,去掉绝对值后还是 \(k-x\),累加就是 \(\sum k\cdot W[k] - x\cdot\sum W[k]\),同样用前缀和一次算出。
  • \(k=x\) 的项贡献为 \(0\),直接从左右两侧里排除,公式更干净。

这就是用「前缀和 + 判断左右」代替逐项遍历的关键。


复杂度

  • 统计两条对角线权重:\(O(N^2)\)
  • 建立前缀和:\(O(N)\)
  • 每个格子查询:\(O(1)\)

总时间复杂度:

\[O(N^2) \]

总空间复杂度:

\[O(N^2) \]


#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;

void solve() {
    int n, m; cin >> n >> m;
    vector<int> A(n + 1), B(n + 1);
    for(int i = 1; i <= n; i ++) cin >> A[i];
    for(int i = 1; i <= n; i ++) cin >> B[i];

    vector<vector<ll>> w(n + 1, vector<ll>(n + 1, 0));
    for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) w[i][j] = (ll)A[i] * B[j] % m;

    //s + t = k, s - t = k
    vector<ll> linest(n * 2 + 1, 0), line_st(n * 2 + 1, 0);
    for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) {
        linest[i + j] += w[i][j];
        line_st[i - j + n] += w[i][j];
    }
    vector<ll> prest(n * 2 + 1, 0), pre_st(n * 2 + 1, 0); //对角线w 前缀和
    vector<ll> weist(n * 2 + 1, 0), wei_st(n * 2 + 1, 0); //对角线w * (s +- t)的带权前缀和
    for(int i = 2; i <= 2 * n; i ++) {
        prest[i] = prest[i - 1] + linest[i];
        weist[i] = weist[i - 1] + (ll)i * linest[i];
    }
    for(int i = 1; i < 2 * n; i ++) {
        pre_st[i] = pre_st[i - 1] + line_st[i];
        wei_st[i] = wei_st[i - 1] + (ll)i * line_st[i];
    }
    
    vector<vector<ll>> f(n + 1, vector<ll>(n + 1, 0));
    for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) {
        int x = i + j, y = i - j + n;
        //s + t = k
        ll cnt1 = x * prest[x - 1] - weist[x - 1];
        ll cnt2 = weist[2 * n] - weist[x] - x * (prest[2 * n] - prest[x]);
        ll tol1 = cnt1 + cnt2;
        //s - t = k
        ll cnt3 = y * pre_st[y - 1] - wei_st[y - 1];
        ll cnt4 = wei_st[2 * n - 1] - wei_st[y] - y * (pre_st[2 * n - 1] - pre_st[y]);
        ll tol2 = cnt3 + cnt4;

        f[i][j] = (tol1 + tol2) / 2;
    }

    ll ans = 0;
    for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) ans ^= (f[i][j] + (i - 1) * n + j - 1);
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; //cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-11-03 21:22  thenrify  阅读(1)  评论(0)    收藏  举报