先算完整 n×m 网格所有出生点对的曼哈顿距离和,再减去所有涉及障碍点的贡献,最后补回障碍之间被多减的一次。
OJ: luogu
题目 ID: P6692
难度:提高+/省选-
标签:数学推导曼哈顿距离组合计数思维
日期: 2026-06-20 07:09
题意
在一个 n x m 的网格里,有 k 个障碍点不能出生。
小 W 和小 H 会各自随机出生在一个非障碍点上。
如果两人交换出生点,视为同一种情况;两人也可以出生在同一个点上。
要求输出:
- 所有合法出生点安排中,两人曼哈顿距离的总和
思路
先看一个可以直接验证的小数据暴力:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m >> k;
set<pair<int, int>> blocked;
for (int i = 0; i < k; i++) {
int x, y;
cin >> x >> y;
blocked.insert({x, y});
}
vector<pair<int, int>> pts;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!blocked.count({i, j})) {
pts.push_back({i, j});
}
}
}
i64 ans = 0;
for (int i = 0; i < (int) pts.size(); i++) {
for (int j = i; j < (int) pts.size(); j++) {
ans += llabs(pts[i].first - pts[j].first)
+ llabs(pts[i].second - pts[j].second);
}
}
cout << ans << '\n';
return 0;
}暴力版先把所有非障碍点列出来,再枚举所有 i <= j 的点对,直接累加曼哈顿距离。
正解的关键是:
- 先算完整网格的答案
- 再把障碍点带来的影响修正掉
1. 完整网格的答案
如果没有障碍,那么曼哈顿距离可以拆成横纵两个方向分别统计。
先看行坐标部分。
假设两点的行分别是 i < j,那么它们在行方向贡献:
j - i
而列可以随便选,有 m^2 种搭配。
所以行方向总贡献是:
m^2 * sum_{1<=i<j<=n} (j-i)
同理,列方向总贡献是:
n^2 * sum_{1<=i<j<=m} (j-i)
并且有经典公式:
sum_{1<=i<j<=L} (j-i) = L(L-1)(L+1)/6
于是完整网格总答案可以直接算出来。
2. 删除障碍点
设完整网格点集是 U,障碍点集合是 B,真正可出生点集合是 R = U - B。
我们想要的是:
sum_{p,q in R, p<=q} dist(p,q)
可以从完整答案里扣掉所有“涉及障碍点”的点对。
对一个障碍点 (x, y) 来说,它到整张网格所有点的距离和是:
m * sum_{i=1}^n |x-i| + n * sum_{j=1}^m |y-j|
其中:
sum_{i=1}^n |x-i|
可以拆成左边一段和右边一段,直接公式算。
把所有障碍点都这样减掉后,会有一个重复:
- 障碍点之间的点对被减了两次
所以最后还要把“障碍点之间的曼哈顿距离和”补回一次。
3. 最终公式
于是最终答案就是:
- 完整网格总和
- 减去 所有障碍点到整张网格的距离和
- 加上 障碍点之间的无序距离和
障碍点之间的距离和也能拆成 x 和 y 两部分,排序后用前缀和在线性里算完。
4. 为什么要手写大整数
n, m 都能到 1e9,答案量级远远超过 long long。
这题没有取模,所以必须输出完整整数。
本地编译环境没有 boost::multiprecision,所以代码里手写了一个只支持:
- 加法
- 减法
- 乘小整数
- 除小整数
的非负大整数,已经够用。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
struct BigInt {
static const int BASE = 1000000000;
vector<int> d; // 小端存储
BigInt(i64 x = 0) {
if (x == 0) {
d.push_back(0);
return;
}
while (x > 0) {
d.push_back((int) (x % BASE));
x /= BASE;
}
}
void trim() {
while (d.size() > 1 && d.back() == 0) {
d.pop_back();
}
}
BigInt &operator += (const BigInt &other) {
int n = max(d.size(), other.d.size());
d.resize(n, 0);
i64 carry = 0;
for (int i = 0; i < n; i++) {
i64 cur = carry + d[i];
if (i < (int) other.d.size()) {
cur += other.d[i];
}
d[i] = (int) (cur % BASE);
carry = cur / BASE;
}
if (carry) {
d.push_back((int) carry);
}
return *this;
}
BigInt &operator -= (const BigInt &other) {
i64 borrow = 0;
for (int i = 0; i < (int) d.size(); i++) {
i64 cur = (i64) d[i] - borrow;
if (i < (int) other.d.size()) {
cur -= other.d[i];
}
if (cur < 0) {
cur += BASE;
borrow = 1;
}
else {
borrow = 0;
}
d[i] = (int) cur;
}
trim();
return *this;
}
BigInt mul_small(i64 x) const {
if (x == 0) {
return BigInt(0);
}
BigInt res;
res.d.assign(d.size(), 0);
i64 carry = 0;
for (int i = 0; i < (int) d.size(); i++) {
i64 cur = (i64) d[i] * x + carry;
res.d[i] = (int) (cur % BASE);
carry = cur / BASE;
}
while (carry > 0) {
res.d.push_back((int) (carry % BASE));
carry /= BASE;
}
res.trim();
return res;
}
BigInt div_small(int x) const {
BigInt res;
res.d.assign(d.size(), 0);
i64 rem = 0;
for (int i = (int) d.size() - 1; i >= 0; i--) {
i64 cur = rem * BASE + d[i];
res.d[i] = (int) (cur / x);
rem = cur % x;
}
res.trim();
return res;
}
string to_string() const {
string s = std::to_string(d.back());
char buf[16];
for (int i = (int) d.size() - 2; i >= 0; i--) {
snprintf(buf, sizeof(buf), "%09d", d[i]);
s += buf;
}
return s;
}
};
BigInt operator + (BigInt a, const BigInt &b) {
a += b;
return a;
}
BigInt operator - (BigInt a, const BigInt &b) {
a -= b;
return a;
}
int k;
i64 n, m;
vector<i64> xs, ys;
// 计算 pos 到 1..len 所有点的距离和。
BigInt sum_to_all(i64 len, i64 pos) {
BigInt left = BigInt(pos - 1).mul_small(pos).div_small(2);
i64 right_cnt = len - pos;
BigInt right = BigInt(right_cnt).mul_small(right_cnt + 1).div_small(2);
return left + right;
}
// 计算一个有序数组中,两两差值和。
BigInt pair_diff_sum(vector<i64> a) {
sort(a.begin(), a.end());
BigInt ans(0);
BigInt prefix(0);
for (int i = 0; i < (int) a.size(); i++) {
BigInt cur((i64) a[i] * i);
cur -= prefix;
ans += cur;
prefix += BigInt(a[i]);
}
return ans;
}
BigInt line_pair_sum(i64 len) {
// sum_{1<=i<j<=len} (j-i) = len*(len-1)*(len+1)/6
BigInt res(len);
res = res.mul_small(len - 1);
res = res.mul_small(len + 1);
res = res.div_small(6);
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> k;
xs.resize(k);
ys.resize(k);
for (int i = 0; i < k; i++) {
cin >> xs[i] >> ys[i];
}
// 完整 n*m 网格中,所有无序点对的曼哈顿距离总和。
BigInt total_full = line_pair_sum(n).mul_small(m).mul_small(m)
+ line_pair_sum(m).mul_small(n).mul_small(n);
// 障碍点到整张网格所有点的距离和(每个障碍点只算一次)。
BigInt remove_sum(0);
for (int i = 0; i < k; i++) {
remove_sum += sum_to_all(n, xs[i]).mul_small(m);
remove_sum += sum_to_all(m, ys[i]).mul_small(n);
}
// 障碍点之间的无序距离和。前面 remove_sum 把这些对减了两次,这里补回一次。
BigInt blocked_pair_sum = pair_diff_sum(xs) + pair_diff_sum(ys);
BigInt answer = total_full + blocked_pair_sum;
answer -= remove_sum;
cout << answer.to_string() << '\n';
return 0;
}复杂度
设障碍数为 k。
- 读入并统计障碍对整图贡献:
- 排序并计算障碍点之间距离和:
总时间复杂度:
空间复杂度:
总结
这题最核心的想法不是“直接数可出生点”,而是:
- 先算完整网格答案
- 再按障碍做删点修正
因为障碍只有 5e5 个,而整张图可能有 1e18 个点,所以必须把复杂度压到只和障碍数有关。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

