Helvetic Coding Contest 2018 online mirror (teams allowed, unrated) E3. Guard Duty (hard) (计算几何 凸包)

题目

2n(n<=1e4)个二维点,其中n个构成p数组,n个构成q数组,

求p和q的一组完美匹配,使得将两两匹配的点连成线段后,任意两条线段不交

题目保证无三点共线 

思路来源

 G - No Cross Matching

quality代码

题解

atcoder373G出了个这题的弱化版,n=300,

那个暴力分治就可以了,所以来补这个n=1e4的题了

这是一个O(n^2)的做法,比较巧妙,每次O(n)找到一组点,然后递归左右

先将全体按x从小到大排序,x相同按y从小到大排序,后面递归也维持这个顺序不变

考虑当前vector内的点,

1. 如果第一个点和最后一个点颜色相同(即来自同一个数组)

不妨都来自p,则一定可以找到一个分界线,

前缀内满足p和q的个数相同,后缀也满足,

于是可以继续分治前缀和后缀

2. 如果第一个点和最后一个点颜色不同,

并且由于已按从小到大排序,则这两个点一定会在这些点构成的凸包上,

O(n)求出这些点的凸包,不妨是下凸壳,

首尾颜色不同的话,则一定能找到一个分界,使得凸包上两个相邻的点颜色也不同

删掉这两个点后,递归剩下的点

由于每次操作是O(n)的,所以复杂度是O(n^2)的

这题有O(nlognlogX)的做法,但是比较复杂难写,不如这个好理解,就先只补这个了

代码

#include<bits/stdc++.h>
using namespace std;
const int MAXN=20005;
struct Point
{
    int x,y;
    explicit Point(int _x=0,int _y=0):x(_x),y(_y) {}
    Point operator - (const Point& t)const
    {
        return Point(x-t.x,y-t.y);
    }
    int operator * (const Point& t)const
    {
        return x*t.y-y*t.x;
    }
    bool operator < (const Point& t)const
    {
        return x==t.x ? y<t.y : x<t.x;
    }
} p[MAXN];
int col[MAXN],res[MAXN];
void solve(vector<int> cur)
{
    if(cur.empty())return;
    if(col[cur.front()]==col[cur.back()]) // 一个最左一个最右 肯定都在凸包上 一定可以找到sum=0的分界线
    {
        vector<int> lef,rig;
        int now=0,flag=0;
        for(auto u : cur)
        {
            if(!flag)lef.push_back(u);
            else rig.push_back(u);
            now+=(col[u]==col[cur.front()] ? 1 : -1);
            flag|=(now==0);
        }
        solve(lef),solve(rig);
        return;
    }
    vector<int> stk;
    for(auto u : cur)
    {
        while(stk.size()>1 && (p[u]-p[stk.back()])*(p[stk.back()]-p[stk[stk.size()-2]])>=0)
            stk.pop_back();
        stk.push_back(u);
    }
    for(size_t i=0; i+1<stk.size(); i++) // 最左和最右异色 求凸包后 一定可以找到颜色不同的两个相邻点
        if(col[stk[i]]!=col[stk[i+1]])
        {
            res[stk[i]]=stk[i+1];
            res[stk[i+1]]=stk[i];
            cur.erase(find(cur.begin(),cur.end(),stk[i]));
            cur.erase(find(cur.begin(),cur.end(),stk[i+1]));
            break;
        }
    solve(cur);
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
        scanf("%d%d",&p[i].x,&p[i].y),col[i]=0;
    for(int i=n+1; i<=2*n; i++)
        scanf("%d%d",&p[i].x,&p[i].y),col[i]=1;
    vector<int> cur(2*n);
    iota(cur.begin(),cur.end(),1);
    sort(cur.begin(),cur.end(),[](int x,int y)
    {
        return p[x]<p[y];
    });
    solve(cur);
    for(int i=1; i<=n; i++)
        printf("%d\n",res[i]-n);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值