Description
假设手机在连接 WiFi 时总是连接最近的路由器, 因此拿着手机移动时, 随着与每个路由器距离的变化会进行路由器(相同SSID)连接的转换.
畅春园宿舍楼的每一间宿舍都安装了一个路由器, 路由器安装的具体位置不固定, Alice 拿着手机在宿舍楼附近移动, 现在他想知道移动的过程中共进行了多少次路由器连接的转换.
为了简化问题, 现在假设 Alice 每次选择一个起点和一个终点, 移动的路线为直线.
Input
输入的第一行为两个正整数 n , m (n ∈ [3,50], m ∈[2,50]), n 表示 Alice 移动的可能起点或终点的坐标个数, m 表示宿舍楼安装的路由器个数.
随后 n 行, 每一行两个整数 x, y∈[0,1000], 表示起点或终点的坐标.
接下来 m 行,每一行两个整数 x, y∈[0,1000], 表示路由器位置的坐标.
接下来一行为一个整数 k, 表示 Alice 实验的次数.
后续 k 行, 每一行两个正整数 a, b∈[1,n], a 表示起点坐标的序号, b 表示终点坐标的序号. (序号指的是前面输入起点和终点坐标的顺序)
Output
对于 Alice 的每次实验, 输出单独的一行, 表示实验过程中路由器连接转换的次数.
定义点如下:
class Point{
public:
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
};
由于所给的坐标都是整数,点与点之间最近距离为 1 ,所以我们可以选择一个合适的步长来模拟 Alice 从起点到终点的过程。
不妨取步长 stepX = 0.5,如果起点到终点的线段垂直于 x 轴,只改变 y 即可,否则 y 方向上的步长 stepY 可以由 x 的步长 stepX 与斜率相乘获得。
double stepX, stepY;
if (start.x == end.x){
stepX = 0;
stepY = 0.5;
}
else{
double k = (end.y - start.y) / (end.x - start.x);
stepX = 0.5;
stepY = stepX * k;
}
定义动点 s ,从起点出发。
Point s = start;
只要动点未到达终点,每次求得距离动点当前位置最近的路由器 nextN,与上一步最近路由器 nearest 比较,若不同则说明切换了路由器,保存切换次数的 res 加一。
while (s.x <= end.x){
int nextN = getNearestRouter(s);
if (nextN != nearest){
res++;
nearest = nextN;
}
s.x += stepX;
s.y += stepY;
}
其中 getNearestRouter(s) 返回距离点 s 最近的路由器编号。
int getNearestRouter(const Point &p){
int nearest = 0; //初值取p和第一个路由器的距离
double dist = getDist(p, router[nearest]);
for (int i = 1; i < m; i++){
double d = getDist(p, router[i]);
if (d < dist){
nearest = i;
dist = d;
}
}
return nearest;
}
由于默认动点坐标加上步长,所以注意调整起点位置,使得起点在终点左侧或正下方。不满足条件的话互换起点与终点即可。
if (points[start].x < points[end].x)
cout << countConnections2(points[start], points[end]) << endl; //左侧
else if (points[start].x == points[end].x){
if (points[start].y < points[end].y)
cout << countConnections2(points[start], points[end]) << endl; //正下方
else
cout << countConnections2(points[end], points[start]) << endl; //正上方
}
else
cout << countConnections2(points[end], points[start]) << endl; //右侧
总体代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
class Point{
public:
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
};
int n, m;
vector<Point> points;
vector<Point> router;
double getDist(const Point &p1, const Point &p2){
double x = p1.x - p2.x;
double y = p1.y - p2.y;
return sqrt(x * x + y * y);
}
int getNearestRouter(const Point &p){
int nearest = 0;
double dist = getDist(p, router[nearest]);
for (int i = 1; i < m; i++){
double d = getDist(p, router[i]);
if (d < dist){
nearest = i;
dist = d;
}
}
return nearest;
}
int countConnections2(Point &start, Point &end){
Point s = start;
double stepX, stepY;
if (start.x == end.x){
stepX = 0;
stepY = 0.5;
}
else{
double k = (end.y - start.y) / (end.x - start.x);
stepX = 0.5;
stepY = stepX * k;
}
int res = -1;
int nearest = -1; //没有序号为 -1 的路由器
while (s.x <= end.x){
int nextN = getNearestRouter(s);
if (nextN != nearest){
res++;
nearest = nextN;
}
s.x += stepX;
s.y += stepY;
}
return res;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++){
Point temp;
cin >> temp.x >> temp.y;
points.push_back(temp);
}
for (int i = 0; i < m; i ++){
Point temp;
cin >> temp.x >> temp.y;
router.push_back(temp);
}
int k;
cin >> k;
while (k --){
int start, end;
cin >> start >> end;
start--;
end--;
if (points[start].x < points[end].x)
cout << countConnections2(points[start], points[end]) << endl; //左侧
else if (points[start].x == points[end].x){
if (points[start].y < points[end].y)
cout << countConnections2(points[start], points[end]) << endl; // 正下方
else
cout << countConnections2(points[end], points[start]) << endl; //正上方
}
else
cout << countConnections2(points[end], points[start]) << endl; //右侧
}
return 0;
}
借鉴大佬思路实现的另一种方法是:
考虑到从起点到终点的过程中,发生路由器切换的点一定位于切换双方所连线段的垂直平分线上,此时该切换点距离两路由器距离相等。
我们可以先取得距离起点最近的路由器作为连接路由器,然后为该路由器和其他所以路由器作垂直平分线。注意若有多个路由器与起点距离相同且最近,取这些路由器中距离终点最近的那一个。
int nearest = 0;
double nearestDist = getDist(start, router[nearest]);
for (int i = 1; i < m; i ++){
double d = getDist(start, router[i]);
if (fabs(d - nearestDist) < 1e-5){ //距离相等
double d1 = getDist(router[nearest], end);
double d2 = getDist(router[i], end);
if (d2 < d1){ //替换为离终点更近的
nearest = i;
nearestDist = d;
}
}
else if (d < nearestDist){ //有更近的路由器
nearest = i;
nearestDist = d;
}
} //找到了最近的路由器编号
1. 若垂直平分线与路径线段未相交,那么一定没有切换点,未发生路由器切换
2. 若有 n (n >= 1)条垂直平分线与路径线段相交,取距离起点最近的交点作为这一次的切换点,其余交点无效。
记录切换次数的 res 加一,选中的交点作为新的起点,选中的垂直平分线对应的另一个路由器为新的连接路由器,循环上述过程,直到无法发生切换,即可得到结果。
整体代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
class Point{
public:
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
};
int n, m;
vector<Point> points;
vector<Point> router;
class Line{ // ax + by = c
public:
double a, b, c;
Line(const Point &p1, const Point &p2, bool signal){
if (signal){ //垂直平分线
a = 2 * (p2.x - p1.x);
b = 2 * (p2.y - p1.y);
c = p2.x * p2.x + p2.y * p2.y - p1.x *p1.x - p1.y * p1.y;
}
else{ //两点连线
a = p1.y - p2.y;
b = p2.x - p1.x;
c = p2.x * p1.y - p1.x * p2.y;
}
}
};
Point crossPoint(const Line &l1, const Line &l2){ //克拉默法则求交点
Point cross;
if (l1.a * l2.b == l2.a * l1.b){ //平行,(-1, -1)一定不满足between()
cross.x = -1;
cross.y = -1;
}
else{
cross.x = (l1.c * l2.b - l2.c * l1.b) / (l1.a * l2.b - l2.a * l1.b);
cross.y = (l1.a * l2.c - l2.a * l1.c) / (l1.a * l2.b - l2.a * l1.b);
}
return cross;
}
bool between(const Point &p, const Point &start, const Point &end){
if (start.x == end.x) return (p.y - start.y) * (p.y - end.y) < 0; //垂直线
else return (p.x - start.x) * (p.x - end.x) < 0;
}
double getDist(const Point &p1, const Point &p2){ //求两点间距离
double x = p1.x - p2.x;
double y = p1.y - p2.y;
return sqrt(x * x + y * y);
}
int countConnections(Point &start, Point &end){
Line s2e(start, end, false);
int nearest = 0;
double nearestDist = getDist(start, router[nearest]);
for (int i = 1; i < m; i ++){
double d = getDist(start, router[i]);
if (fabs(d - nearestDist) < 1e-5){ //距离相等
double d1 = getDist(router[nearest], end);
double d2 = getDist(router[i], end);
if (d2 < d1){ //替换为离终点更近的
nearest = i;
nearestDist = d;
}
}
else if (d < nearestDist){ //有更近的路由器
nearest = i;
nearestDist = d;
}
} //找到了最近的路由器编号
int res = 0;
Point C = start;
while (true){
Point nextC = end;
int nextID = -1;
for (int i = 0; i < m; i ++){
if (nearest == i) continue;
Line routerLine(router[nearest], router[i], true);
Point cross = crossPoint(routerLine, s2e);
if (between(cross, C, nextC)){ //为了找最近,将区间右端点前移
nextC = cross;
nextID = i;
}
}
//无交点
if (nextID == -1) break;
else{
nearest = nextID;
C = nextC;
res ++;
}
}
return res;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++){
Point temp;
cin >> temp.x >> temp.y;
points.push_back(temp);
}
for (int i = 0; i < m; i ++){
Point temp;
cin >> temp.x >> temp.y;
router.push_back(temp);
}
int k;
cin >> k;
while (k --){
int start, end;
cin >> start >> end;
start--;
end--;
cout << countConnections(points[start], points[end]) << endl;
}
return 0;
}