常用距离算法
常用距离算法
对于两个点
- 欧氏距离
- 曼哈顿距离
- 切比雪夫距离
三维情况下表示为
多维情况下表示为
1、欧几里得距离
二维形式:
三维形式:
多维形式:
更多的是并查集的应用,理论上 long long
,但是这题数据过水?不会爆 。代码用了 __int128
。
#include <bits/stdc++.h>
using namespace std;
typedef __int128 int128;
const int N = 1e5 + 10;
int T, n, h, r, fa[N], down[N], up[N]; // 能否到下表面/上表面
struct node {
int x, y, z;
} a[N];
int Find(int x) {
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
void Union(int x, int y) {
int fx = Find(x), fy = Find(y);
up[fy] |= up[fx], down[fy] |= down[fx], fa[fx] = fy; // 合并状态
}
int128 dic(int i, int j) {
int128 dx = a[i].x - a[j].x, dy = a[i].y - a[j].y, dz = a[i].z - a[j].z;
return (int128) dx * dx + dy * dy + dz * dz; // 这里可能会爆 long long
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> T;
while (T--) {
cin >> n >> h >> r;
int x, y, z;
for (int i = 1; i <= n; i++) {
up[i] = down[i] = 0, fa[i] = i;
cin >> x >> y >> z;
a[i] = {x, y, z};
if (z <= r) down[i] = 1;
if (h - z <= r) up[i] = 1;
}
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++)
if (dic(i, j) <= (int128) 4 * r * r)
Union(i, j);
bool flag = false;
for (int i = 1; i <= n; i++) {
if (fa[i] == i && up[i] && down[i]) {
flag = true;
break;
}
}
if (flag) cout << "Yes" << '\n';
else cout << "No" << '\n';
}
return 0;
}
2、曼哈顿距离
二维形式:
三维形式:
多维形式:
代数含义
考虑二维形式,不妨设
- 当
时, . - 当
时, .
发现只与
P5098 [USACO04OPEN] Cave Cows 3 双倍经验 [ABC178E] Dist Max
给出
还是分两种考虑,第一种情况时,
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int n, x, y;
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int a = -INF, b = -INF, c = INF, d = INF;
for (int i = 1; i <= n; i++) {
cin >> x >> y;
a = max(a, x + y);
b = max(b, x - y);
c = min(c, x + y);
d = min(d, x - y);
}
cout << max(a - c, b - d) << '\n';
return 0;
}
几何含义
观察一下,不难发现点
- 在平面直角坐标系中,与点
的哈曼顿距离相等的点组成的集合为 的直线
回到这道题,可以发现答案一定由这两种情况构成:
- 最左上的点和最右下的点的曼哈顿距离
- 最左下的点和最右上的点的曼哈顿距离
其中最角落的点怎么定义?观察
结合直线方程,发现最左上角的点所在直线就是
回到这道题,只需要记录
- 最左上角:
,其中 对应上个代码的 - 最左下角:
,其中 对应上个代码的 - 最右下角:
,其中 对应上个代码的 - 最右上角:
,其中 对应上个代码的
这时考虑最左上的直线和最右下的直线的曼哈顿距离怎么计算:令
和上一题一样,可以先找
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, a, b, c, d, x, y, pos, dic = 1e18;
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> n;
a = b = -1e18, c = d = 1e18;
for (int i = 1; i <= n; i++) {
cin >> x >> y;
a = max(a, x + y), b = max(b, x - y), c = min(c, x + y), d = min(d, x - y);
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
int d1 = max(a - (x + y), (x + y) - c);
int d2 = max(b - (x - y), (x - y) - d);
if (max(d1, d2) < dic) {
pos = i, dic = max(d1, d2);
}
}
cout << dic << '\n' << pos << '\n';
return 0;
}
3、切比雪夫距离
二维形式:
三维形式:
多维形式:
没找到模板题,但是其一个绝对值比曼哈顿距离的两个绝对值在排序、比较时显然更有优越性。但是在其他某些情况显然曼哈顿距离更好计算。所以这两者的相互转化对解题就极为关键了。
4、曼哈顿距离和切比雪夫距离的转换
转换指在将坐标系
代数证明
考虑化简
接下来拆开点
而
然后拆开点
观察到
这样的话
这样,就成功代换了两个坐标系中的点,总结一下结论:
在坐标系
同样的,
观察到坐标除以 long long
。
这种证明会比几何证明更加严谨(毕竟几何证明只是靠观察)。
几何证明
红色的正方形(坐标系
容易发现:将坐标系
设
可以发现就是上面的结论,只是
切比雪夫距离转为曼哈顿距离。可以直接转换,转换后就很简单了。按
拆完式子之后用前缀和就可以
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, x[N], y[N], px[N], py[N];
struct node {
int x, y;
} a[N];
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int r, t;
for (int i = 1; i <= n; i++) {
cin >> r >> t;
x[i] = a[i].x = r + t, y[i] = a[i].y = r - t;
}
sort(x + 1, x + 1 + n);
sort(y + 1, y + 1 + n);
for (int i = 1; i <= n; i++) px[i] = px[i - 1] + x[i], py[i] = py[i - 1] + y[i]; // 处理前缀和
int ans = 1e18;
for (int i = 1; i <= n; i++) {
int X = lower_bound(x + 1, x + 1 + n, a[i].x) - x;
int Y = lower_bound(y + 1, y + 1 + n, a[i].y) - y;
int wx = (2 * X - n) * a[i].x + px[n] - 2 * px[X];
int wy = (2 * Y - n) * a[i].y + py[n] - 2 * py[Y];
ans = min(ans, wx + wy);
}
cout << ans / 2 << '\n';
return 0;
}
一道很妙的曼哈顿转切比雪夫问题。转化后还要转化为
#include <bits/stdc++.h>
using namespace std;
int n, m, d;
char s[4] = {'R', 'Y', 'G', 'B'};
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> d;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int x = (i + j) / d, y = (i - j + m) / d;
cout << s[((y & 1) << 1) + (x & 1)];
}
cout << '\n';
}
return 0;
}
[JOISC 2021 Day2] 道路の建設案 (Road Construction)
考虑到曼哈顿距离的双绝对值很难进行排序等操作,转化为切比雪夫距离。整体二分距离 set
里二分 check
函数,最后 check(f - 1)
,不够的用
#include <bits/stdc++.h>
#define int long long
#define pii pair <int, int>
using namespace std;
const int N = 3e5 + 10;
int n, k, ans[N], cnt;
struct coor {
int x, y;
friend bool operator < (const coor &a, const coor &b) {return a.x == b.x ? a.y < b.y : a.x < b.x;}
} a[N];
struct node {
int x, y;
friend bool operator <(const node &a, const node &b) {return a.y < b.y;}
};
int check(int mid) {
multiset <node> m;
queue<int> q;
cnt = 0;
for (int r = 1; r <= n; r++) {
while (!q.empty() && a[q.front()].x < a[r].x - mid) {
auto it = m.find({a[q.front()].x, a[q.front()].y});
m.erase(it), q.pop();
}
auto pos = m.lower_bound({0, a[r].y - mid});
while (pos != m.end() && (*pos).y <= a[r].y + mid) {
ans[++cnt] = max(abs(a[r].x - (*pos).x), abs((*pos).y - a[r].y));
pos++;
}
m.insert({a[r].x, a[r].y}), q.push(r);
if (cnt >= k) return cnt;
}
return cnt;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> k;
int x, y;
for (int i = 1; i <= n; i++) {
cin >> x >> y;
a[i] = {x + y, x - y};
}
sort(a + 1, a + 1 + n);
int l = -1e18, r = 1e18, mid = 0, f = 0;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid) >= k) r = mid - 1, f = mid;
else l = mid + 1;
}
check(f - 1);
sort(ans + 1, ans + 1 + cnt);
for (int i = 1; i <= cnt; i++) cout << ans[i] << '\n';
for (int i = cnt + 1; i <= k; i++) cout << f << '\n';
return 0;
}
这道题和上一道题一样的,转换后问题转变为求 set
的迭代器 pos
操作后是否为 s.begin()
和 s.end()
,以及 pos--
的顺序。复杂度
#include <bits/stdc++.h>
#define int long long
#define pii pair <int, int>
using namespace std;
const int N = 1e5 + 10;
int n, r, fa[N], j = 1, x, y, res;
struct noor {
int x, y;
friend bool operator < (const noor &a, const noor &b) {
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
} a[N];
int Find(int x) {return x == fa[x] ? x : fa[x] = Find(fa[x]);}
void Union(int x, int y) {fa[Find(x)] = Find(fa[y]);}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> r;
for (int i = 1; i <= n; i++) {
cin >> x >> y;
a[i] = {x + y, x - y}, fa[i] = i;
}
sort(a + 1, a + 1 + n);
multiset <pii> s;
for (int i = 1; i <= n; i++) {
while (j < i && a[j].x < a[i].x - r) s.erase(s.find({a[j].y, j})), j++;
auto pos = s.lower_bound({a[i].y, i});
if (pos != s.end() && (*pos).first <= a[i].y + r) Union((*pos).second, i);
if (pos != s.begin()) {
pos--;
if ((*pos).first >= a[i].y - r) Union((*pos).second, i);
}
s.insert({a[i].y, i});
}
for (int i = 1; i <= n; i++) res += (Find(i) == i);
cout << res << '\n';
return 0;
}
这道题转换完后和这道 P10452 货仓选址 的思路是一模一样的,就是找中位数。只不过这道题找到的点在原坐标系中可能不是整数,要再判一下周围的点。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
typedef unsigned long long ull;
int n, X[N], Y[N];
struct node {
int x, y, t;
} u[N], a[N];
bool cmp1(node a, node b) {return a.x == b.x ? a.y < b.y : a.x < b.x;}
bool cmp2(node a, node b) {return a.y == b.y ? a.x < b.x : a.y < b.y;}
int init(int X[N], int cmp) {
if (cmp == 1) sort(a + 1, a + 1 + n, cmp1);
else sort(a + 1, a + 1 + n, cmp2);
for (int i = 1; i <= n; i++) X[i] = X[i - 1] + a[i].t;
int ax = upper_bound(X + 1, X + 1 + n, X[n] / 2) - X;
if (cmp == 1) return a[ax].x;
else return a[ax].y;
}
ull check(int x, int y) {
ull ans = 0ull;
for (int i = 1; i <= n; i++) ans += 1ull * max(abs(u[i].x - x), abs(u[i].y - y)) * u[i].t;
return ans;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int x, y, t;
for (int i = 1; i <= n; i++) {
cin >> x >> y >> t;
u[i] = {x, y, t}, a[i] = {x + y, x - y, t};
}
int ax = init(X, 1), ay = init(Y, 2);
x = (ax + ay) / 2, y = (ax - ay) / 2;
ull s1 = check(x + 1, y + 1);
ull s2 = check(x + 1, y);
ull s3 = check(x, y + 1);
ull s4 = check(x, y);
ull m = min(min(s1, s2), min(s3, s4));
if (s1 == m) cout << x + 1 << ' ' << y + 1 << '\n';
else if (s2 == m) cout << x + 1 << ' ' << y << '\n';
else if (s3 == m) cout << x << ' ' << y + 1 << '\n';
else if (s4 == m) cout << x << ' ' << y << '\n';
return 0;
}
5、以上问题的三维及更高维
坐标转换
三维形式
设
又因为
按照和二维一样的配凑法,找相反数,然后点对点配对,一共
而这就是
容易发现为除了
高维形式
定义
设
其中,
不难发现:由于
总结一下结论:
统计切比雪夫距离小于 的点对数
可以发现上面处理二维问题的复杂度为
拓展到 query
时用容斥,这样时间复杂度可以降到
根据上面的结论对三个维度分别做就行了。
#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e5 + 10, M = 75010, P = 80;
int B, n, d, m, a[N], c[M * 2], t[P * 3][P * 3][P * 3];
struct noor {
int x, y;
friend bool operator < (const noor &a, const noor &b) {
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
} b[N];
struct node {
int a, b, c, d;
friend bool operator < (const node &a, const node &b) {
return a.a < b.a;
}
} u[N];
int lowbit(int x) {
return x& -x;
}
void add(int i, int x) {
for (; i < M * 2; i += lowbit(i)) c[i] += x;
}
int query(int i) {
i = min(i, 2 * M - 1);
int ans = 0;
for (; i > 0; i -= lowbit(i)) ans += c[i];
return ans;
}
inline void Add(int x, int y, int z, int v) {
for (int i = x; i < P * 3; i += i & -i) {
for (int j = y; j < P * 3; j += j & -j) {
for (int k = z; k < P * 3; k += k & -k) {
t[i][j][k] += v;
}
}
}
}
inline int Query(int x, int y, int z) {
x = min(x, P * 3 - 1), y = min(y, P * 3 - 1), z = min(z, P * 3 - 1);
int res = 0;
for (int i = x; i > 0; i -= i & -i) {
for (int j = y; j > 0; j -= j & -j) {
for (int k = z; k > 0; k -= k & -k) {
res += t[i][j][k];
}
}
}
return res;
}
int ask(int x1, int x2, int y1, int y2, int z1, int z2) {
int ans = Query(x2, y2, z2);
ans -= Query(x1, y2, z2) + Query(x2, y1, z2) + Query(x2, y2, z1);
ans += Query(x1, y1, z2) + Query(x1, y2, z1) + Query(x2, y1, z1);
ans -= Query(x1, y1, z1);
return ans;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> B >> n >> d >> m;
if (B == 1) {
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
int pos = 1, ans = 0;
for (int i = 1; i <= n; i++) {
while (pos <= n && a[pos] - a[i] <= d) pos++;
ans += pos - i - 1;
}
cout << ans << '\n';
} else if (B == 2) {
int x, y, j = 1, cnt = 0;
for (int i = 1; i <= n; i++) {
cin >> x >> y, b[i] = {x + y, x - y};
}
sort(b + 1, b + 1 + n);
for (int i = 1; i <= n; i++) {
while (j < i && b[j].x < b[i].x - d) add(b[j].y + m, -1), j++;
cnt += query(b[i].y + d + m) - query(b[i].y - d - 1 + m);
add(b[i].y + m, 1);
}
cout << cnt << '\n';
} else if (B == 3) {
int x, y, z, j = 1, cnt = 0;
for (int i = 1; i <= n; i++) {
cin >> x >> y >> z;
u[i] = {x + y + z, x + y - z, x - y + z, -x + y + z};
}
sort(u + 1, u + 1 + n);
for (int i = 1; i <= n; i++) {
while (j < i && u[j].a < u[i].a - d) {
Add(u[j].b + m, u[j].c + m, u[j].d + m, -1);
j++;
}
cnt += ask(u[i].b - d - 1 + m, u[i].b + d + m, u[i].c - d - 1 + m, u[i].c + d + m, u[i].d - d - 1 + m, u[i].d + d + m);
Add(u[i].b + m, u[i].c + m, u[i].d + m, 1);
}
cout << cnt << '\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架