把滚轮相切关系建成图,从驱动轮到目标轮找到唯一路径,再按半径比递推各滚轮转速并累加绝对值。
OJ: luogu
题目 ID: P2903
难度:普及+/提高
标签:图论dfs模拟USACO
日期: 2026-06-19 08:42
题意
给出 n 个滚轮的位置和半径。
- 坐标为
(0,0)的滚轮是驱动轮; - 坐标为
(Xt,Yt)的滚轮是目标滚轮; - 两个滚轮如果外切,就可以传递动力。
已知驱动轮每小时顺时针转 10000 圈。若半径为 Rd 的滚轮以速度 S 驱动半径为 Rx 的滚轮,那么后者速度变成:
-S * Rd / Rx
负号表示转向相反。
题目要我们只看“从驱动轮到目标滚轮”这一条动力链,把这条链上所有滚轮速度的绝对值加起来,最后输出截断后的整数部分。
思路
最直接的想法,是把所有相切关系建成图,然后从驱动轮一路搜到目标滚轮。搜索时顺手把当前滚轮的转速也传下去,走到目标时就能得到这条路径上的答案。
这个版本最适合理解题意:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1055;
int n;
int target_x, target_y;
int x[MAXN], y[MAXN], r[MAXN];
vector<int> g[MAXN];
int root_id, target_id;
bool vis[MAXN];
bool found_answer;
long double answer_sum;
bool touch(int i, int j) {
long long dx = 1LL * x[i] - x[j];
long long dy = 1LL * y[i] - y[j];
long long sum_r = 1LL * r[i] + r[j];
return dx * dx + dy * dy == sum_r * sum_r;
}
void build_graph() {
for (int i = 1; i <= n; i++) {
g[i].clear();
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (touch(i, j)) {
g[i].push_back(j);
g[j].push_back(i);
}
}
}
}
void find_special_nodes() {
root_id = 0;
target_id = 0;
for (int i = 1; i <= n; i++) {
if (x[i] == 0 && y[i] == 0) {
root_id = i;
}
if (x[i] == target_x && y[i] == target_y) {
target_id = i;
}
}
}
// 朴素做法:在相切图里直接搜从驱动轮到目标轮的那条路径,
// 搜索过程中把当前滚轮速度和前面路径上的速度和一起传下去。
void dfs(int u, long double speed_now, long double sum_now) {
if (found_answer) {
return;
}
vis[u] = true;
sum_now += fabsl(speed_now);
if (u == target_id) {
answer_sum = sum_now;
found_answer = true;
vis[u] = false;
return;
}
for (int i = 0; i < (int) g[u].size(); i++) {
int v = g[u][i];
if (vis[v]) {
continue;
}
long double next_speed = -speed_now * (long double) r[u] / r[v];
dfs(v, next_speed, sum_now);
}
vis[u] = false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> target_x >> target_y;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i] >> r[i];
}
build_graph();
find_special_nodes();
found_answer = false;
answer_sum = 0.0L;
memset(vis, 0, sizeof(vis));
dfs(root_id, 10000.0L, 0.0L);
cout << (long long) floorl(answer_sum + 1e-12L) << '\n';
return 0;
}如何判断两个滚轮相连
如果两个滚轮外切,那么圆心距离恰好等于半径和。
所以对于滚轮 i 和 j,只要判断:
(xi-xj)^2 + (yi-yj)^2 == (ri+rj)^2
就知道它们之间有没有边。
为什么路径是唯一的
题目保证:
- 除驱动轮外,每个滚轮都由某个别的滚轮驱动;
- 一个滚轮不会同时被两个滚轮驱动。
这意味着对于目标滚轮来说,从驱动轮走到它的动力传递链只有一条,所以我们找到目标后,沿着父节点回溯即可。
转速怎么传
若当前滚轮速度是 S,半径是 r[u],相邻滚轮半径是 r[v],那么:
speed[v] = -speed[u] * r[u] / r[v]
因此在 BFS / DFS 过程中,把父节点和转速一起记录下来即可。
最后从目标滚轮一直回溯到驱动轮,把这些滚轮的 |speed| 累加,就是答案。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1055;
int n;
int target_x, target_y;
int x[MAXN], y[MAXN], r[MAXN];
vector<int> g[MAXN];
int root_id, target_id;
int parent_node[MAXN];
long double speed[MAXN];
bool touch(int i, int j) {
long long dx = 1LL * x[i] - x[j];
long long dy = 1LL * y[i] - y[j];
long long sum_r = 1LL * r[i] + r[j];
return dx * dx + dy * dy == sum_r * sum_r;
}
void build_graph() {
for (int i = 1; i <= n; i++) {
g[i].clear();
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (touch(i, j)) {
g[i].push_back(j);
g[j].push_back(i);
}
}
}
}
void find_special_nodes() {
root_id = 0;
target_id = 0;
for (int i = 1; i <= n; i++) {
if (x[i] == 0 && y[i] == 0) {
root_id = i;
}
if (x[i] == target_x && y[i] == target_y) {
target_id = i;
}
}
}
void bfs() {
memset(parent_node, -1, sizeof(parent_node));
queue<int> q;
parent_node[root_id] = 0;
speed[root_id] = 10000.0L;
q.push(root_id);
while (!q.empty()) {
int u = q.front();
q.pop();
if (u == target_id) {
return;
}
for (int i = 0; i < (int) g[u].size(); i++) {
int v = g[u][i];
if (parent_node[v] != -1) {
continue;
}
parent_node[v] = u;
// 两个相切滚轮的线速度相同,所以角速度与半径成反比,方向相反。
speed[v] = -speed[u] * (long double) r[u] / r[v];
q.push(v);
}
}
}
long long calc_answer() {
long double sum = 0.0L;
int cur = target_id;
while (cur != 0) {
sum += fabsl(speed[cur]);
if (cur == root_id) {
break;
}
cur = parent_node[cur];
}
return (long long) floorl(sum + 1e-12L);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> target_x >> target_y;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i] >> r[i];
}
build_graph();
find_special_nodes();
bfs();
cout << calc_answer() << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
其中
总结
这题本质上不是物理题,而是一个“几何建图 + 图上找路径”的题。
先用“圆心距离等于半径和”把相切关系建出来,再沿着从驱动轮到目标滚轮的那条链递推转速,问题就做完了。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
