AcWing4202 穿过圆 (树上最短路 / bitset优化暴力)

题目链接: 穿过圆

大致题意

给定 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;
}

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值