紫书 第8章 UVa1411 Ants 巨人与鬼

题意:给出平面上n个白点n个黑点,要求两两配对,且配对所连线段没有交点。
解题方法:
由于只需要一种配对方法,从直观上来说本题一定是有解的。由于每一个巨人和鬼都需
要找一个目标,不妨先给“最特殊”的巨人或鬼寻找“搭档”。
考虑y坐标最小的点(即最低点)。如果有多个这样的点,考虑最左边的点(即其中最
左边的点),则所有点的极角在范围[0,π)内。不妨设它是一个巨人,然后把所有其他点按照
极角从小到大的顺序排序后依次检查。
情况1:第一个点是鬼,那么配对完成,剩下的巨人和鬼仍然是一样多,而且不会和这
一条线段交叉,如图8-6(a)所示。
情况2:第一个点是巨人,那么继续检查,直到已检查的点中鬼和巨人一样多为止。找
到了这个“鬼和巨人”配对区间后,只需要把此区间内的点配对,再把区域外的点配对即可,
如图8-6(b)所示。这个配对过程是递归的,好比棋盘覆盖中一样。会不会找不到这样的配
对区间呢?不会的。因为检查完第一个点后鬼少一个,而检查完最后一个点时鬼多一个,而
巨人和鬼的数量差每次只能改变1,因此“从少到多”的过程中一定会有“一样多”的时候。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
struct Point{
    int x, y;
    Point(){}
    Point(int x, int y) : x(x), y(y){}
    double angle(const Point &p) const{
        return atan2(y - p.y, x - p.x);
    }
    bool operator < (const Point &rhs) const{
        return y < rhs.y || (y == rhs.y && x < rhs.x);
    }
    void read(){
        scanf("%d%d", &x, &y);
    }
};
struct Node{
    Point p;
    int id;
    double ang;
    bool operator <(const Node &rhs) const{
        return ang < rhs.ang;
    }
    void getangle(const Point &p0){
        ang = p.angle(p0);
    }
    int type() const{
        return id <= n ? 1 : -1;
    }
}p[N * 2];
int ans[N * 2];
void solve(int l, int r){
    if(l > r) return ;
    int pos = l;
    for(int i = l + 1; i <= r; i++){
        if(p[i].p < p[pos].p){
            pos = i;
        }
    }
    swap(p[pos], p[l]);
    int cnt = p[l].type();
    for(int i = l + 1; i <= r; i++){
        p[i].getangle(p[l].p);
    }
    sort(p + l + 1, p + r + 1);
    for(int i = l + 1; i <= r; i++){
        cnt += p[i].type();
        if(cnt == 0){
            ans[p[l].id] = p[i].id;
            ans[p[i].id] = p[l].id;
            solve(l + 1, i - 1);
            solve(i + 1, r);
            return ;
        }
    }
}
int main(){
    while(scanf("%d", &n) != EOF)
    {
        memset(ans, 0, sizeof(ans));
        for(int i = 1; i <= 2 * n; i++){
            p[i].p.read();
            p[i].id = i;
        }
        solve(1, n * 2);
        for(int i = 1; i <= n; i++){
            printf("%d\n", ans[i] - n);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值