【CEOI 2020】 Roads(道路)

Description

政府想修建一个新的公路网,共 2*N 个城市,目前已经完成 N 条公路,每条公路都是用直线连接两个城市,且没有两条路有公共点包括没有公共端点。

你的任务是再修建 N-1 条路,且需要满足以下三个条件:

  1. 每一条路都必须是用一条直线连接两个城市。

  2. 如果两条路(包括新路与旧路、新路与新路之间)有一个公共点,则该点必须是道路的端点。

  3. 新路和旧路共同组成的公路网能连接所有城市,即每对城市都有一条由连接两个城市的路段组成的道路。

Solution

首先为了避免斜率正无穷这种极端情况的出现,可以将点进行一个旋转。 ( x , y ) (x,y) (x,y) 逆时针转 α \alpha α 角后的坐标是 ( x cos ⁡ α − y sin ⁡ α , x sin ⁡ α + y cos ⁡ α ) (x\cos\alpha-y\sin\alpha,x\sin\alpha+y\cos\alpha) (xcosαysinα,xsinα+ycosα)

计算几何题中出现线段需要统计一些结果的,一般使用扫描线来完成。

假设扫描线从左往右扫。扫描过程中,扫描线会不断被给出的线段分割成若干个区域。当扫描线碰到某条线段的左端点时,说明此时又新增了一个区域。那么我们找出此前该区域内 x x x 坐标最大的点,与当前点连边即可。注意更新各区域的 x x x 坐标最大的点。

当扫到线段的右端点时,就将两个区域合并,同时更新一下最大的 x x x 坐标。

具体过程可以用 set \text{set} set 来实现。

Code

#include<set>
#include<cmath>
#include<cstdio>
#include<algorithm>
#define N 100050
#define inf 10000000000
#define ldb long double
using namespace std;
int n;
ldb nowx;
ldb Pi=acos(-1.0),alpha=Pi*(1.0*139/180),cosal=cos(alpha),sinal=sin(alpha);
struct po
{
    int rlx,rly;
    ldb x,y;
    void calc()
    {
        x=cosal*rlx-sinal*rly;
        y=cosal*rly+sinal*rlx;
    }
}a[N<<1];
struct px
{
    int id,tp;
    ldb x;
}c[N<<1];
struct line
{
    int st,ed;mutable int pre;
    ldb k,b;
    void calc()
    {
        k=(a[st].y-a[ed].y)/(a[st].x-a[ed].x);
        b=a[st].y-k*a[st].x;
    }
    bool operator <(const line x) const {return k*nowx+b<x.k*nowx+x.b;};
}now;
set<line> S;
set<line>::iterator it;
bool cmp(px x,px y) {return x.x<y.x;}
int main()
{
    freopen("network.in","r",stdin);
    freopen("network.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=2*n;i+=2)
    {
        scanf("%d%d",&a[i].rlx,&a[i].rly);a[i].calc();
        scanf("%d%d",&a[i+1].rlx,&a[i+1].rly);a[i+1].calc();
        if (a[i].x>a[i+1].x) swap(a[i],a[i+1]);
        c[i].x=a[i].x;c[i].id=i;c[i].tp=1;
        c[i+1].x=a[i+1].x;c[i+1].id=i+1;c[i+1].tp=0;        
    }
    sort(c+1,c+2*n+1,cmp);
    now.st=-2;now.ed=0;now.pre=0;now.k=0;now.b=-inf;S.insert(now);
    now.st=-1;now.ed=0;now.pre=0;now.k=0;now.b=inf;S.insert(now);
    for (int i=1;i<=2*n;++i)
    {
        nowx=c[i].x;
        if (c[i].tp)
        {
            now.pre=now.st=c[i].id;now.ed=c[i].id+1;
            now.calc();
            it=S.insert(now).first;--it;
            if (it->pre) printf("%d %d %d %d\n",a[it->pre].rlx,a[it->pre].rly,a[now.st].rlx,a[now.st].rly);
            it->pre=now.st;
        }
        else
        {
            now.st=c[i].id-1;now.ed=c[i].id;
            now.calc();
            it=S.find(now);--it;
            it->pre=now.ed;
            S.erase(now);
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值