紫书刷题进行中,题解系列【GitHub|CSDN】
例题6-22 UVA11853 Paintball(41行AC代码)
题目大意
给定边长为1000的正方形,左下角和右上角坐标分别为(0,0)和(1000,1000)
,先已知若干圆的圆心坐标和半径,问能否从正方形左侧不穿过任何圆到达右侧?若存在多个出入口,则输出靠近上方的点。
注意在仅仅在圆的内部才算穿过,边界不算
思路分析
看起来无从下手,因为点坐标是无限多的,若是直接寻找一条从左侧到右侧的路径,就会陷入无限状态的死局。又碰到了这种无限的情况,类似UVA12171,于是考虑能够离散化处理,但圆这图形不像长方形,范围难以处理,只好放弃。在细看题目
问题1:什么情况一定无解?
答案1:从上边界到下边界存在若干连续的相交圆
仔细分析题目条件,若那么把圆看成顶点,相交的圆存在边,与边相交则可在边上任取一点作为顶点相连。若是能从左穿到右,那么从上边界一定无法到达下边界,即转换为连通性判定问题
以上思想本质是逆向思维,将空白处和圆对调来考虑问题。
因为初始想法是在空白处找到一条通路,但其存在无限点问题,此时若转为考虑圆,首先圆个数是有限的(类似离散化),解决无线点问题,其次将问题转换为连通性问题,可以回答判定性问题1。
问题2:如何找到左右边界的入出口?
答案2:在所有从上边界开始的连通块中,取与左右边界交点最南的点
这听起来觉得有些荒谬,但是,此时我们已经能判断存在一条从左到右的通路,那么我们大可以忽略内部细节,将路径看成一个黑盒,只注重于它的起点和终点(因为只要垂直方向不被完全阻断,那么无孔不入的你就可以通过)
建议多画几个图看看,比如下图(插图容易丢,凑合着看:),外围表示正方形,内部有两个从顶部开始的连通块,|和*
分别表示连通块A,B,可以比较容易看出,A与左边界交点更南,因此取它的交点,依次类推,推广到多个连通块情况。聪明的你,利用对称法则,肯定可以解决右侧边界的情况。
|------------------|
| | * |
| / * |
|* / * * * |
|- / |
| |
|------------------|
因此做个总结,将圆看成顶点,相交的圆存在无向边,加入坐标信息后,对于每个与上边界相交的圆,以其为起点进行dfs遍历,若能到达与下边界相交的圆,则表示无解;在以上dfs过程中,经过的每个点都是连通块的一部分,此时取其中与左右边界交点的最南端点,即为答案。
注意点
- 两圆相交则表示存在边(不同于相切),本题相交表示坐标间关系都是严格大于或小于,不可取等
AC代码(C++11,条件转换,连通块dfs)
#include<bits/stdc++.h>
using namespace std;
#define SQU(x) ((x)*(x)) // 平方宏,注意都要加括号
const int maxn=1010; // 最大圆数量
int n, vis[maxn]={0}; // 访问数组
double x[maxn], y[maxn], r[maxn], ansl, ansr; // 坐标(x,y);r为半径;左边界最小点,右边界最小点
bool isIntersect(int c1, int c2) { // 判断两个圆是否相交:圆心距 < (r1+r2)
return SQU(x[c1]-x[c2]) + SQU(y[c1]-y[c2]) < SQU(r[c1]+r[c2]);
}
void calLR (int c) { // 计算每个连通块与左右边界最南处交点
if (x[c] < r[c]) { // 与左边界相交
double t = y[c] - sqrt(SQU(r[c]) - SQU(x[c]));
if (t < ansl) ansl = t;
}
if (x[c] + r[c] > 1000) { // 与右边界相交
double t = y[c] - sqrt(SQU(r[c]) - SQU(1000-x[c]));
if (t < ansr) ansr = t;
}
}
bool dfs(int c) { // 判断能否从北边界走到南边界
vis[c] = 1; // 标记为已访问
if (y[c] < r[c]) return true; // 不取等,碰到南边界,即直线y=0
for (int i=0; i < n; i ++) if (vis[i]==0 && isIntersect(c,i) && dfs(i)) return true; // 遍历连通块
calLR(c); // 计算左右边界
return false; // 到这里说明
}
int main() {
while (scanf("%d", &n) == 1) {
for (int i=0; i < n; i ++) scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
memset(vis, 0, sizeof(vis)); ansl = ansr = 1000.0; // 初始化
bool isWin=true;
for (int i=0; i < n; i ++) // 遍历n个圆
if (y[i]+r[i] > 1000 && dfs(i)) { // 与上边界相交且能走到下边界
isWin = false;
break;
}
if (isWin) printf("0.00 %.2lf 1000.00 %.2lf\n", ansl, ansr);
else puts("IMPOSSIBLE");
}
return 0;
}