- admin's blog
test
- @ 2025-7-25 21:51:04
好的,我们来分析一下你的代码和问题。
你的解题思路大方向是正确的:利用坐标变换将切比雪夫距离问题转化为曼哈顿距离问题,然后通过排序和前缀和来优化计算。这是一个非常经典的技巧。
核心思想回顾:
-
切比雪夫距离与曼哈顿距离的转换
- 两个点 和 的切比雪夫距离是 。
- 通过坐标变换,令 , 。
- 可以证明 $max(|x_i - x_j|, |y_i - y_j|) = 1/2 * (|u_i - u_j| + |v_i - v_j|)$。
- 也就是说,原坐标系下的切比雪夫距离,等于新坐标系 下曼哈顿距离的一半。
-
公式展开 总能量 $Ans = ∑_{1≤i<j≤n} (a_i + a_j) * max(|x_i - x_j|, |y_i - y_j|)$ 代入转换公式: $Ans = ∑_{1≤i<j≤n} (a_i + a_j) * 1/2 * (|u_i - u_j| + |v_i - v_j|)$ $Ans = 1/2 * [ ∑_{1≤i<j≤n} (a_i + a_j)|u_i - u_j| + ∑_{1≤i<j≤n} (a_i + a_j)|v_i - v_j| ]$
可以看到,问题被分解成了两个完全独立的一维问题。我们只需要计算出 坐标的总贡献和 坐标的总贡献,相加后除以2即可。
我们以 坐标为例,计算 。
错误分析
你的代码错误在于计算一维问题贡献 (或 ) 的方法。
让我们看看你对 (u坐标) 的处理:
int sum=0;
int sumv=p1[1].second; // sumv 是能量 a_i 的前缀和
for(int i=2;i<=n;i++)
{
sumv+=p1[i].second; // 此时 sumv = a_1 + ... + a_i
// 核心计算步骤
sum+=( sumv*( p1[i].first*inv2-p1[i-1].first*inv2 )%mod )%mod;
sum%=mod;
ans+=sum; // 错误点:这是一个双重累加
ans%=mod;
}
-
双重累加 (ans += sum): 这是最主要的逻辑错误。 本身就是一个累加值,你再把它累加到 里,形成了一个二次求和。这不符合我们推导的任何一个公式。正确的做法应该是将每次循环计算出的贡献值直接加到 上,而不是通过一个中间变量 再累加。
-
贡献计算错误: 即使去掉 ,只看 这一行,其计算逻辑也是不正确的。 这个式子 并没有一个清晰的物理或数学意义能对应到我们想求的 。
正确的计算方法 (前缀和)
我们来推导 的正确计算方法。
-
排序: 首先,将所有点按 坐标从小到大排序。排序后,对于 , 就等于 。
-
贡献拆分: 我们遍历排序后的点(从 到 ),在每个点 计算它与所有在它之前的点 产生的贡献之和。 $Contribution_i = ∑_{j=1}^{i-1} (a_i + a_j)(u_i - u_j)$
-
展开 : $Contribution_i = ∑_{j=1}^{i-1} (a_i*u_i - a_i*u_j + a_j*u_i - a_j*u_j)$ $= ∑_{j=1}^{i-1} a_i*u_i - ∑_{j=1}^{i-1} a_i*u_j + ∑_{j=1}^{i-1} a_j*u_i - ∑_{j=1}^{i-1} a_j*u_j$ $= (i-1)*a_i*u_i - a_i * (∑_{j=1}^{i-1} u_j) + u_i * (∑_{j=1}^{i-1} a_j) - (∑_{j=1}^{i-1} a_j*u_j)$
-
维护前缀和: 为了在 O(1) 时间内得到 ,我们需要维护三个前缀和:
- : (前面所有点的能量和)
- : (前面所有点的u坐标和)
- : (前面所有点能量与u坐标的乘积和)
这样,在遍历到第 个点时,我们就可以用这些前缀和来计算 ,然后更新这些前缀和,为下一个点做准备。
修改后的代码
下面是根据正确逻辑修改后的代码。我将一维问题的求解封装成了一个函数 ,使其结构更清晰。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 12;
const int mod = 1e9 + 7;
const int inv2 = 500000004; // 2 在 mod 1e9+7 下的逆元
// 使用结构体更清晰
struct Point {
int coord; // u 或 v 坐标
int val; // 能量 a
};
bool comparePoints(const Point& a, const Point& b) {
return a.coord < b.coord;
}
// 处理一维问题的函数
int solve_1d(int n, Point p[]) {
sort(p + 1, p + 1 + n, comparePoints);
int total_sum = 0;
int prefix_coord_sum = 0; // ∑ u_j
int prefix_val_sum = 0; // ∑ a_j
int prefix_prod_sum = 0; // ∑ a_j * u_j
for (int i = 1; i <= n; i++) {
int current_coord = p[i].coord;
int current_val = p[i].val;
int count = i - 1;
// 计算 Contribution_i = ∑_{j<i} (a_i + a_j)(u_i - u_j)
// 展开后为: count*a_i*u_i - a_i*∑u_j + u_i*∑a_j - ∑(a_j*u_j)
// 注意处理负数和取模
int term1 = (count % mod * current_val % mod * (current_coord % mod) % mod) % mod;
int term2 = (current_val % mod * (prefix_coord_sum % mod)) % mod;
int term3 = ((current_coord % mod) * (prefix_val_sum % mod)) % mod;
int term4 = prefix_prod_sum % mod;
int contribution = (term1 - term2 + mod) % mod;
contribution = (contribution + term3) % mod;
contribution = (contribution - term4 + mod) % mod;
total_sum = (total_sum + contribution) % mod;
// 更新前缀和,为下一次迭代做准备
prefix_coord_sum = (prefix_coord_sum + current_coord) % mod;
prefix_val_sum = (prefix_val_sum + current_val) % mod;
int new_prod = (current_val % mod * (current_coord % mod)) % mod;
prefix_prod_sum = (prefix_prod_sum + new_prod) % mod;
}
return total_sum;
}
Point p1[N], p2[N];
signed main() {
// 提高cin/cout效率
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int x, y, v;
cin >> x >> y >> v;
p1[i] = {x + y, v};
p2[i] = {x - y, v};
}
int sum_u = solve_1d(n, p1);
int sum_v = solve_1d(n, p2);
int ans = (sum_u + sum_v) % mod;
ans = (ans * inv2) % mod;
// 确保结果为正
cout << (ans + mod) % mod << endl;
}
return 0;
}
总结
- 核心错误: 你的代码中 的双重累加是错误的。
- 公式错误: 单步贡献 的计算公式也是不正确的。
- 正确解法: 将问题分解为两个独立的一维问题。对每个一维问题,通过排序,然后遍历一次,同时维护三个前缀和(坐标和、能量和、坐标与能量的乘积和),来计算每个点与它前面所有点的贡献。
- 实现注意:
- 在进行模运算下的减法时,要 以防止出现负数。
- 坐标 可能是负数,所以 和所有前缀和都可能是负数。在每次取模时都需要小心处理, 在C++中对于负数的结果可能不是我们想要的(例如 结果是 ),一个稳妥的方式是 。我的修改版代码已经考虑了这一点。
- 使用 防止中间乘积溢出。你的 #define int long long 已经做到了。