题目链接: 穿过圆
大致题意
给定 n n n个点, m m m个圆. 保证每个点不会在圆边上, 且圆和圆之间不相交.
有 k k k次询问, 每次询问连线两个点 a , b a, b a,b, 至少需要穿过多少个圆.
解题思路
思路一: 转化为树上最短路问题
我们考虑到由于题目保证圆与圆不相交, 且点不在圆上. 那么如果我设全集为第 m + 1 m+1 m+1个圆, 则 n n n个点必然都包含在 m + 1 m+1 m+1个圆之中.
如果我们认为某个点 p p p所属圆是: 包含点 p p p, 且半径最小的. 则每个点也必然有一个所属圆.
我们考虑每次询问两点 a , b a, b a,b:
①若 a , b a, b a,b在同一所属圆内, 则答案为 0 0 0.
②若 a , b a, b a,b不在同一所属圆, 则我们从点 a , b a, b a,b开始连线, 穿过仅包含点 a a a或点 b b b的圆边, 直到遇到第一个同时包含 a , b a, b a,b的圆时两线相连, 此时应为最优解.
我们发现②很像树中最近公共祖先的概念. 如果把该问题抽象到树上, 相当于是求边权为 1 1 1的树上任意两点最短路径. 我们可以通过树上差分解决.
思路二: 优化暴力
通过思路一的分析, 我们可以得出结论: 对于要连线的点 a , b a, b a,b 穿过最少圆的数量 = 仅包含点a的圆数量 + 仅包含点b的圆数量
.
但是考虑到有 1 0 5 10^5 105次询问, 1 0 3 10^3 103个圆, 我们的时间复杂度达到了 1 0 8 10^8 108, 难以在 0.3 s 0.3s 0.3s的时限内通过.
我们观察问题本质: 我们需要取得一个在 a a a不在 b b b, 在 b b b不在 a a a的集合, 这本质是一个求集合的异或操作, 我们可以通过 b i t s e t bitset bitset优化实现.
➡️如果不会bitset优化集合问题, 可以先做做这个题⬅️
AC代码
转化为树上最短路
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E3 + 10;
pair<int, int> p[N];
struct node {
int x, y, r; } cir[N];
int in[N];
bool fact(const pair<int, int> a, const node& b) {
int x1 = a.first, y1 = a.second;
int x2 = b.x, y2 = b.y; int r = b.r;
int x = x1 - x2, y = y1 - y2;
return 1ll * x * x + 1ll * y * y <= 1ll * r * r;
}
vector<int> edge[N];
/* 倍增求LCA模版 */
namespace LCA {
const int B = 10; // 记录真实的B, f数组多开1即可
int dep[N], f[N][B + 1];
void dfs(int x = 1, int fa = 0) {
dep[x] = dep[fa] + 1;
f[x][0] = fa;
rep(i, B) f[x][i] = f[f[x][i - 1]][i - 1];
for (auto& to : edge[x]) if (to != fa) dfs(to, x);
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = B; i >= 0; --i) if (dep[f[x][i]] >= dep[y]) x = f[x][i];
if (x == y) return x;
for (int i = B; i >= 0; --i) if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
}
int main()
{
int n, m, k; cin >> n >> m >> k;
rep(i, n) {
int x, y; scanf("%d %d", &x, &y);
p[i] = {
x, y };
}
rep(i, m) {
int r, x, y; scanf("%d %d %d", &r, &x, &y);
cir[i] = {
x, y, r };
}
rep(i, n) {
rep(j, m) {
if (fact(p[i], cir[j])) {
if (!in[i] or cir[in[i]].r > cir[j].r) in[i] = j;
}
}
if (!in[i]) in[i] = m + 1;
}
rep(i, m) {
int index = 0;
rep(j, m) {
if (cir[j].r > cir[i].r and fact({
cir[i].x, cir[i].y }, cir[j])) {
if (!index or cir[index].r > cir[j].r) index = j;
}
}
if (!index) index = m + 1;
edge[index].push_back(i), edge[i].push_back(index);
}
LCA::dfs();
rep(i, k) {
int a, b; scanf("%d %d", &a, &b);
a = in[a], b = in[b];
int lca = LCA::lca(a, b);
int res = LCA::dep[a] + LCA::dep[b] - 2 * LCA::dep[lca];
printf("%d\n", res);
}
return 0;
}
bitset优化暴力
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E3 + 10;
pair<int, int> p[N];
bitset<N> bt[N];
struct node {
int x, y, r; }cir[N];
bool fact(const pair<int, int>& a, const node& b) {
int x1 = a.first, y1 = a.second;
int x2 = b.x, y2 = b.y;
int x = x1 - x2, y = y1 - y2;
return sqrt(1ll * x * x + 1ll * y * y) < b.r;
}
int main()
{
int n, m, k; cin >> n >> m >> k;
rep(i, n) {
int x, y; scanf("%d %d", &x, &y);
p[i] = {
x, y };
}
rep(i, m) {
int r, x, y; scanf("%d %d %d", &r, &x, &y);
cir[i] = {
x, y, r };
}
rep(i, n) rep(j, m) {
bt[i][j] = fact(p[i], cir[j]);
}
rep(i, k) {
int a, b; scanf("%d %d", &a, &b);
printf("%d\n", (bt[a] ^ bt[b]).count());
}
return 0;
}