2017 CDQZ 联训 Day2 T1 十字形

我太菜了,写一发题解,代码改了好几天。。

十字形

【问题描述】

有一个平面直角坐标系,上面有一些线段,保证这些线段至少与一条坐标轴平行。我们需要计算出,这些线段构成的最大的十字形有多大。

称一个图形为大小为R的十字形,当且仅当,这个图形具有一个中心点,它存在于某一条线段上,并且由该点向上下左右延伸出的长度为R的线段都被已有的线段覆盖。

你可以假定:没有两条共线的段具公点,没有重合的线段。

【输入格式】

从文件 cross .in 中输入 数

第一行,个正整数 N,代表线段的数目。

以下 N行,每四个整数一行,每四个整数 x1,y1,x2,y2(x1=x2或 y1=y2,描述了一条线段。 )

【输出格式】

输出到文件 cross.out 中。

当不存在十字型时:输出一行“Human intelligence is really terrible”(不包括引号)。

否则:输出一行,一个整数为最大的 R。

【样例】

样例输入1:

1

0 0 0 1

样例输出1:

Human intelligence is really terrible

样例输入2:

>

3

-1 0 5 0

0 -1 0 1

2 -2 2 2

样例输出2:

2

【数据规模与约定】

对于 50% 的数据, N1000

对于 100% 的数据, 1N100000 ,所有坐标的范围在 109109 之间。

后 50% 内,所有数据均为随机生成。

【解题思路】

因为后 50% 的数据均为随机生成,所以本题各种乱搞都可以AC,然而我这个智障写了一个满的 n2 暴力。。这些乱搞的方法大概分两种,一种是把水平和竖直的线段分别按长度排序,暴力判交,如果当前线段的长度不大于当前答案长度的二倍,直接break。。

考虑稳定时间复杂度 nlogn 的做法。

首先把一个最小化的问题转化为判定性问题,如何判断是否存在一个大小大于等于x的十字型。这个问题就等价于,先把所有线段都从两端截掉x的长度(长度小于2x的线段直接删掉,等于2x的不能删),然后判断这些线段是否存在交点(有公共端点也算)。可以用set维护扫描线。

我们让一条横着的扫描线从下向上扫,每一条横坐标为x0的竖直直线就相当于两个事件——插入事件和删除事件,在这两个事件之间,扫描线上x0的位置有线段存在。每个水平的的线段可以看成是一个查询事件,查询扫描线在[x1,x2]区间上是否存在元素,若存在证明有交点。把所有事件按照y坐标从小到大排序即可。

【代码】

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
struct lin{int x1,y1,x2,y2;};//lin- line
vector<lin>ud,lr;//ud- up&down lr- left&right
//lr, ud 分别储存水平竖直线段
struct evnt{int op,l,r,t;};//event
//op==0 query [l,r]
//op==1 insert l (r=1) / erase l (r=-1)
bool cmp(evnt a,evnt b){
    if(a.t!=b.t)return a.t<b.t;
    return a.op*a.r>b.op*b.r;//先增,再查,最后删 
}
bool ch(int x){  //ch- check 判断x是否可行
    vector<evnt>ns;ns.clear();//ns记录所有事件
    for(int i=0;i<ud.size();i++){//insert/erase event
        int l=ud[i].y2-ud[i].y1;
        if(2*x>l)continue;
        ns.push_back((evnt){1,ud[i].x1,1,ud[i].y1+x});
        //insert
        ns.push_back((evnt){1,ud[i].x1,-1,ud[i].y2-x});
        //erase
    }
    for(int i=0;i<lr.size();i++){//query event
        int l=lr[i].x2-lr[i].x1;
        if(2*x>l)continue;
        ns.push_back((evnt)
        {0,lr[i].x1+x,lr[i].x2-x,lr[i].y1});
    }//check
    sort(ns.begin(),ns.end(),cmp);//把询问按时间排序
    set<int>exist;exist.clear();
    for(int i=0;i<ns.size();i++){
        if(ns[i].op==1)//insert/erase
            if(ns[i].r==1){
                exist.insert(ns[i].l);//insert
            }else{
                exist.erase(ns[i].l);//erase
            }
        else{//query
            if(exist.lower_bound(ns[i].l)
                !=exist.upper_bound(ns[i].r)){
                return true;//找到交点
            }
        }
    }
    ns.clear();exist.clear();
    return false;//没有交点
}
int main(){
    freopen("cross.in","r",stdin);
    freopen("cross.out","w",stdout);
    int N;scanf("%d",&N);
    for(int i=1;i<=N;i++){
        int x1,x2,y1,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        if(y1>y2)swap(y1,y2);
        if(x1>x2)swap(x1,x2);
        if(x1==x2){//分别储存
            ud.push_back((lin){x1,y1,x2,y2});
        }else{
            lr.push_back((lin){x1,y1,x2,y2});
        }
    }
    if(!ch(1)){//连大小为1的十字形都没有,说明不存在十字形
        printf("Human intelligence is really terrible\n");
        return 0;
    }
    int l=1,r=1000000000;//[L,R]
    while(r-l>1){//二分答案 直至区间长度为2
        int mid=(l+r)/2;
        if(ch(mid))l=mid;
        else r=mid;
    }
    if(!ch(r))printf("%d\n",l);//优先把r作为答案
    else printf("%d\n",r);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值