【GDOI2018模拟7.6】仰望星空

Description

由于原题看了好多遍才真正理解题意,这里讲一个简化版本
给出平面上的一个圆和n个点
现在要再这n个点之间两两连边
每个点只能被连一次,只有圆内和圆外之间距离不超过d的点可以互相连边
且如果存在三个圆内的未被连边的点x,y,z,与圆外一点w的距离不超过d
现在若想连接w和y,并且x与z的连线与w,y的连线相交,那么这个连线是不合法的
求最大的连线数目和方案
n<=1e3

Solution

显然最大匹配
考虑怎么处理这个鬼畜的限制
发现执行匈牙利算法的时候我们每个点都会按顺序匹配
于是我们队每个圆外的点,将圆内的点关于它做一次极角排序,得到它的遍历顺序
然后直接做匈牙利就可以解决这个神奇的限制得到答案
注意方案必须要按顺序,所以每个点必须等它之前的匹配全部输出之后才能输出

并不知道atan2用来极角排序哪里错了

bool cmp(point a,point b) 
{return atan2(a.y-rt.y,a.x-rt.x)<atan2(b.y-rt.y,b.x-rt.x);}

改成叉积就对了,求dalao解释

bool cmp(point a,point b) {return cro(a,b,rt)>0;}

UPD:错因很显然然而我现在才发现一定是我太弱了_ (:з」∠) _
这道题排极角序必须是严格按顺序,不能有一个起点
然而用atan2就默认第3象限最小,这样会有问题

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define sqr(x) (x)*(x)
using namespace std;
typedef double db;
const int N=1e3+5;
int n,r,d,tot,ans,b[N],c[N],g[N][N],to[N],link[N];
db eps=1e-5;
bool vis[N],bz[N];
struct point{int x,y,id;}a[N],p[N],rt;
int cro(point a,point b,point x) {return (a.x-x.x)*(b.y-x.y)-(a.y-x.y)*(b.x-x.x);}
db dist(point a,point b) {return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}
bool cmp(point a,point b) {return cro(a,b,rt)>0;}
int find(int x) {
    fo(i,1,g[x][0]) 
        if (!vis[g[x][i]]) {
            vis[g[x][i]]=1;
            if (!to[g[x][i]]||find(to[g[x][i]])) {
                to[g[x][i]]=x;
                return 1;
            }
        }
    return 0;
}
int main() {
    freopen("B.in","r",stdin);
    scanf("%d%d%d",&n,&r,&d);
    fo(i,1,n) scanf("%d%d",&a[i].x,&a[i].y),a[i].id=i;
    fo(i,1,n) {
        if (dist(a[i],a[1])-r<eps) b[++b[0]]=i;
        else c[++c[0]]=i;
    }
    fo(i,1,c[0]) {
        tot=0;rt=a[c[i]];
        fo(j,1,b[0]) 
            if (dist(a[b[j]],a[c[i]])-d<eps) 
                p[++tot]=a[b[j]];
        sort(p+1,p+tot+1,cmp);int x=a[c[i]].id;
        fo(j,1,tot) g[x][++g[x][0]]=p[j].id;
    }
    fo(i,1,c[0]) {
        memset(vis,0,sizeof(vis));
        ans+=find(a[c[i]].id);
    }
    printf("%d\n",ans*2);
    fo(i,1,b[0]) link[to[a[b[i]].id]]=a[b[i]].id;
    fo(i,1,ans) {
        bool pd=0;int now=0;
        fo(j,1,c[0]) {
            int x=a[c[j]].id;
            fo(k,1,g[x][0]) 
                if (!bz[g[x][k]]) {
                    if (g[x][k]==link[x]) pd=1;
                    now=g[x][k];break;
                }
            if (pd) {
                bz[now]=1;
                printf("%d %d\n",x,now);
                break;
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值