今天我们来接触一个有趣的几何问题,主角是一群蚂蚁
题意
首先我们给出传送门:https://vjudge.net/problem/UVA-1411
这个题目大概是这个意思:有n只蚂蚁和n棵苹果树,这n只蚂蚁想去吃苹果,蚂蚁和苹果树是一一对应的(卧槽,这么可怕!),一只蚂蚁只能上一棵树,一棵树上也只能有一只蚂蚁,然后题目会给出n个蚂蚁和n个树的位置,你要规划出哪只蚂蚁对应哪棵树,蚂蚁的行走路线必须是直线。当然没有这么简单,最重要的条件是,蚂蚁的行走路线不能相交。
不能相交这个条件就非常地烧脑了,首先我们必须建立一个比较合理地选取点的规则,不然到最后我们可能会算出一团乱麻,所以,怎么保证现在选取的路线不会使以后的蚂蚁无路可走,这是一个核心问题。
分析
首先这是一道分治的题目,那就更加提示了我们这道题具有普遍性的规律。那我们可以想到,一条路线是有什么组成的?就是一只蚂蚁和一棵树,那么假设我们迭代蚂蚁作为出发点,将对面的树作为可选择的选项,这样的模式似乎是可行的。好,那我们看似找到了一点方向。
但是我们换个角度思考,对于路线不重叠,重要的并不再是蚂蚁或者树谁找谁这个问题了,而是在于平面上的位置,正如我的标题所讲这是一道几何题,题目给的数据也是几何数据,所以我们必须使用几何的知识来解决问题。
我们可以自己在纸上模拟一下匹配,会发现一个比较有意思的事情。有两种情况的连线你一定有把握:
1.这条线在最边缘,根本没有分割平面内的其他点(就是其他点都在这条线的一个方向上),那么我连接这条线对其他的点根本就没影响嘛!下图就是这种情况。

2.这条线分割了平面上的点,但是有趣的是,它分割得非常“优美”,它两个方向上的蚂蚁和树的数量都是相等的,就像下图这样。

所以,我们就有了方案,我们可以利用这两种情况进行分治,我们再品味一下这一种算法会发现,其实第一种也算是第二种情况,因为0==0
并非不可以啊,所以,我们也把第一种省略了。
然后我们可以选取一个固定的锚点,算法设计入门经典是选取了最下面的点(因为最方便计算sin角,使范围缩到0°-180°),然后我们通过计算sin角可以算出相应的位置,我们就从与x轴0角度开始扫向与x轴180度,然后对我经过的点进行计数,一旦发现线条左边和右边相等就可以配对了。
需要注意一点,我们会定义两个变量来对树和蚂蚁进行计数,千万不要忘记要将锚点算进去。意思就是假设锚点是蚂蚁,那么蚂蚁的数量一开始就是1,而树的数量一开始是0;假设锚点是树,那么树的数量一开始就是1了,蚂蚁则是0。这一点非常关键,否则无法进行计算。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <algorithm> #include <iostream> #include <cmath> using namespace std;
struct Point{ int x,y; int num,color; double tilt; }p[300]; int ans[300];
double cal(Point s,Point a){ return (a.x-s.x)/sqrt((a.x-s.x)*(a.x-s.x)+(a.y-s.y)*(a.y-s.y)); }
void divide(int left,int right){ if(left+1>=right) return; sort(p+left,p+right,[](Point A,Point B){return A.y==B.y?A.x<B.x:A.y<B.y;}); int color=p[left].color; for(int i=left+1;i<right;i++) p[i].tilt=cal(p[left],p[i]); sort(p+left+1,p+right,[](Point A,Point B){return A.tilt>B.tilt;}); int s1=1,s2=0; for(int i=left+1;i<right;i++){ if(p[i].color==color) s1++; else s2++; if(s1==s2){ if(color==0) ans[p[left].num]=p[i].num; else ans[p[i].num]=p[left].num; divide(left+1,i); divide(i+1,right); break; } } }
int main(){ ios::sync_with_stdio(false); cin.tie(0); int n; for(int cas=0;cin>>n;cas++){ if(cas) cout << endl; for(int i=0;i<n;i++){ cin>>p[i].x>>p[i].y; p[i].color=0; p[i].num=i+1; } for(int i=n;i<2*n;i++){ cin>>p[i].x>>p[i].y; p[i].color=1; p[i].num=i-n+1; } divide(0,2*n); for(int i=1;i<=n;i++) cout << ans[i] << endl; } return 0; }
|
然后就可以愉快地AC了!