题目
2n(n<=1e4)个二维点,其中n个构成p数组,n个构成q数组,
求p和q的一组完美匹配,使得将两两匹配的点连成线段后,任意两条线段不交
题目保证无三点共线
思路来源
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;
}