bzoj3165: [Heoi2013]Segment【线段树+计算几何】

Description

要求在平面直角坐标系下维护两个操作:
1.在平面上加入一条线段。记第i条被插入的线段的标号为i。
2.给定一个数k,询问与直线 x = k相交的线段中,交点最靠上的线段的编号。

Input

第一行一个整数n,表示共n 个操作。
接下来n行,每行第一个数为0或1。

若该数为 0,则后面跟着一个正整数 k,表示询问与直线
x = ((k +lastans–1)%39989+1)相交的线段中交点(包括在端点相交的情形)最靠上的线段的编号,其中%表示取余。若某条线段为直线的一部分,则视作直线与线段交于该线段y坐标最大处。若有多条线段符合要求,输出编号最小的线段的编号。
若该数为 1,则后面跟着四个正整数 x0, y0, x 1, y 1,表示插入一条两个端点为
((x0+lastans-1)%39989+1,(y0+lastans-1)%10^9+1)和((x1+lastans-1)%39989+1,(y1+lastans-1)%10^9+1) 的线段。
其中lastans为上一次询问的答案。初始时lastans=0。

Output

对于每个 0操作,输出一行,包含一个正整数,表示交点最靠上的线段的编号。若不存在与直线相交的线段,答案为0。

Sample Input

6

1 8 5 10 8

1 6 7 2 6

0 2

0 9

1 4 7 6 7

0 5

Sample Output

2

0 3

HINT

对于100%的数据,1 ≤ n ≤ 10^5 , 1 ≤ k, x0, x1 ≤ 39989, 1 ≤ y0 ≤ y1 ≤ 10^9。

解题思路:

线段树的神奇应用。

由于坐标都是正整数且x范围很小,所以考虑对x坐标建一棵线段树。
比较直接的想法是每个节点维护覆盖这个区间最上面的折线,但显然会被卡。

考虑每个节点只记覆盖这个区间的一条线段。
插入一条线段,一直递归线段数到覆盖了该区间。
如果当前区间没有被线段覆盖,那么就直接更新。
如果当前区间被线段覆盖了,如果两个线段没有交点,那么取高的那一个,否则取形成折线中x范围长的那个,那么拿短的线段去更新儿子,这样可以保证只会更新线段树的一条路径。

查询:
从根节点开始一直查询到底,中间每一条线段都要计算。

因为线段会被拆成 O(logn) O ( l o g n ) 个区间,每个区间又要向下延伸 O(logn) O ( l o g n ) 步,所以复杂度是 O(nlog2n) O ( n l o g 2 n ) 的。

#include<bits/stdc++.h>
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=40000;
int m,n=40000,cnt,ans;
int x[N<<2],y[N<<2],id[N<<2];
double K[N<<2],ansy;

double Y(int x0,int y0,double k,int qx){return (double)y0+k*(qx-x0);}

void modify(int k,int l,int r,int ql,int qr,int qx,int qy,double qK,int i)
{
    int mid=l+r>>1;
    if(l==ql&&r==qr)
    {
        if(!id[k]){x[k]=qx,y[k]=qy,K[k]=qK,id[k]=i;return;}
        double ly1=Y(x[k],y[k],K[k],l),ry1=Y(x[k],y[k],K[k],r);
        double ly2=Y(qx,qy,qK,l),ry2=Y(qx,qy,qK,r);
        if(ly1>=ly2&&ry1>=ry2)return;
        if(ly1<=ly2&&ry1<=ry2){x[k]=qx,y[k]=qy,K[k]=qK,id[k]=i;return;}
        double x0=((double)y[k]-(double)qy+qK*qx-K[k]*x[k])/(qK-K[k]);
        if(ly1<ly2)
            if(x0<=mid)modify(k<<1,l,mid,l,mid,qx,qy,qK,i);
            else
            {
                modify(k<<1|1,mid+1,r,mid+1,r,x[k],y[k],K[k],id[k]);
                x[k]=qx,y[k]=qy,K[k]=qK,id[k]=i;
            }
        else
            if(x0<=mid)
            {
                modify(k<<1,l,mid,l,mid,x[k],y[k],K[k],id[k]);
                x[k]=qx,y[k]=qy,K[k]=qK,id[k]=i;
            }
            else modify(k<<1|1,mid+1,r,mid+1,r,qx,qy,qK,i);
    }
    if(qr<=mid)modify(k<<1,l,mid,ql,qr,qx,qy,qK,i);
    else if(ql>mid)modify(k<<1|1,mid+1,r,ql,qr,qx,qy,qK,i);
    else modify(k<<1,l,mid,ql,mid,qx,qy,qK,i),modify(k<<1|1,mid+1,r,mid+1,qr,qx,qy,qK,i);
}

void query(int k,int l,int r,int qx)
{
    double tmpy=Y(x[k],y[k],K[k],qx);
    if(tmpy>ansy||tmpy==ansy&&id[k]<ans)ansy=tmpy,ans=id[k];
    if(l==r)return;
    int mid=l+r>>1;
    if(qx<=mid)query(k<<1,l,mid,qx);
    else query(k<<1|1,mid+1,r,qx);
}

int main()
{
    int op,X1,X2,Y1,Y2;double qK;
    m=getint();
    while(m--)
    {
        op=getint();
        if(!op)
        {
            X1=(getint()+ans-1)%39989+1;
            ans=0,ansy=0;
            query(1,1,n,X1);
            printf("%d\n",ans);
        }
        else
        {
            X1=(getint()+ans-1)%39989+1;
            Y1=(getint()+ans-1)%1000000000+1;
            X2=(getint()+ans-1)%39989+1;
            Y2=(getint()+ans-1)%1000000000+1;
            if(X1>X2)swap(X1,X2),swap(Y1,Y2);
            if(X1==X2)Y1=max(Y1,Y2),qK=0;
            else qK=(Y2-Y1)*1.0/(X2-X1);
            modify(1,1,n,X1,X2,X1,Y1,qK,++cnt);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值