[UOJ191][集训队互测2016]Unknown-线段树-斜率优化

Unknown

Description

原题目名字是“我们仍未知道那天所看见的数据结构的名字”,由于原题目名太长就叫Unknown了……

我们,渐渐地长大了。在这缓缓逝去的季节里,屏幕上闪烁的字符,也在静静地变化着。

那个季节里编写的数据结构,叫什么名字来着呢?

慢慢地,OI渐渐地淡去。而我们则在不断成长,但是那个程序一定仍在某个时空里继续运行着。

Salroey忘了那个数据结构的名字和内容,但她却记得题目,于是她来寻求你的帮助。

有一个元素为向量的序列,下标从1开始,初始时为空,现在你需要支持三个操作:

1.在SS的末尾添加一个元素(x,y)(x,y)。

2.删除SS的末尾元素。

3.询问下标在[l,r][l,r]区间内的元素中,(x,y)×Si(x,y)×Si的最大值。

其中××表示向量的叉积,(x1,y1)×(x2,y2)=x1y2−x2y1(x1,y1)×(x2,y2)=x1y2−x2y1

Input

第一行一个整数 tptp 表示数据类型,接下来输入多组数据(不超过 33 组),以 m=0m=0 结束,对于每组数据:

第一行一个整数 mm 表示操作数。

接下来行,每行有三种格式:

1 x y 在的末尾添加一个元素 (x,y)(x,y)
2 删除的末尾元素

3 l r x y 询问下标在区间 [l,r][l,r] 内的元素中,(x,y)×Si(x,y)×Si的最大值。

Output

为避免过多输出,你要将所有询问答案对M=998244353取模(数学意义上,取模的结果在[0,M)区间内),并输出取模后答案的异或和,每组数据一行。

Sample Input

7
6
1 -5 10
3 1 1 7 -9
2
1 2 6
1 -3 5
3 1 2 10 -7
0

Sample Output

83

Hint

对于所有数据:

0≤tp≤7,1≤m≤5000000≤tp≤7,1≤m≤500000。

任意时刻 n≥0n≥0, nn 为当前序列长度。

1操作个数不超过300000300000,且满足 −109≤x≤109;1≤y≤109−109≤x≤109;1≤y≤109。

3操作个数不超过300000,且满足 1≤x≤109;−109≤y≤1091≤x≤109;−109≤y≤109。

m≤500000,内存限制为64M


WA也就算了,TLE也不管了,MLE咱也认了,但是……
这是什么意思啊啊啊啊啊啊啊
Dangerous Syscalls

(╯‵□′)╯︵┻━┻


思路:
考虑把这个奇怪的叉积弄好看一点:
设三个向量分别为 (x,y) , (x1,y1) , (x2,y2) ,其中 x2<x1
(x,y)×(x1,y1)>(x,y)×(x2,y2)
xy1x1y>xy2x2y
x(y1y2)>y(x1x2)
注意到 x1 ,那么
y1y2x1x2>yx
那么就可以建凸包,并用斜率二分了~

然而这个凸包不好建。
因为如果一段区间的点全部因为不够优而被弹掉了的话,那将询问不到任何东西。
那么显然要试图用尽可能少的凸包来描述一段区间。

考虑使用线段树来执行这个过程。
两个儿子分别建立凸包,再在父亲处用类似归并排序的方法合并。
但是考虑到这样一次经过 O(logn) 个区间,每个区间重构 O(n) ,复杂度无法承受。

观察发现,一段区间有可能被纳入答案,当且仅当现有的点数大于等于其右区间。
那么考虑优化成,一旦区间被填满,重构当前区间凸包。
然而这样做最坏复杂度仍是与上面相同的,只需要造一组在区间右端点反复横跳的数据就能卡掉这个方法。

考虑引入替罪羊思想,只有当同一深度的下一个区间被填满了才重构当前区间。
询问时若扫到未重构的区间,递归左右儿子继续寻找答案。
可以发现这样仍会落在 O(logn) 个区间上,因为只有最右边的一列区间有没被更新过的可能,而这样的区间只有 O(logn) 个。

然后每次询问在访问到的区间的凸包上二分查找最优解,总询问复杂度 O(log22n)

最后是一些小细节:
如果直接在一个区间被填满时重构上一个区间有可能会出问题。
因为此时咱们的树未必是一颗满二叉树,有些区间不存在“下一个区间”,无法被重构,在某些写法的情况下会WA(比如咱)。
解决方法只有一种:开成满二叉树,然后动态开空间。
但这样做就有可能获得满屏鲜红的Dangerous Syscalls

还咱一晚上青春啊啊啊

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;
const int N=(1<<19)+10;
const int n=(1<<19);
const ll md=998244353;
#define mid ((l+r)>>1)

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x*f;
}

inline void chkmax(ll &a,ll b){if(a<b)a=b;}

struct point
{
    int x,y;
    point(){}
    point(int _x,int _y){x=_x;y=_y;}
    bool operator < (const point &o)const
    {
        if(x==o.x)return y<o.y;
        return x<o.x;
    }
    point operator - (const point &o)const
    {
        return point(x-o.x,y-o.y);
    }
    friend ll cross(const point &a,const point &b)
    {
        return (ll)a.x*(ll)b.y-(ll)b.x*(ll)a.y;
    }
};

struct hull
{
    point *stk;
    int top;
    inline void init(int siz)
    {
        stk=new point[siz+1];
        top=0;
    }
    inline void clear(){top=0;}
    const point &operator [] (int x)const{return stk[x];}
    inline void push_back(const point &o)
    {
        while(top>1 && cross(o-stk[top-1],stk[top]-stk[top-1])<=0)
            top--;
        stk[++top]=o;
    }
    inline void merge(const hull &a,const hull &b)
    {
        delete stk;
        init(a.top+b.top+1);
        for(int pa=1,pb=1;pa<=a.top || pb<=b.top;)
        {
            if(pb>b.top || (pa<=a.top && a[pa]<b[pb]))
                push_back(a[pa++]);
            else
                push_back(b[pb++]);
        }
    }
    inline ll query(const point &o)
    {
        int l=1,r=top;
        while(l<r)
        {
            if(cross(o,stk[mid+1]-stk[mid])>=0)
                l=mid+1;
            else
                r=mid;
        }
        return cross(o,stk[l]);
    }
};

struct segment_tree
{
    hull t[N<<2];
    int top,lborder[30];

    inline void build(int x,int l,int r,int dep=0)
    {
        if(l==1)lborder[dep]=x;
        if(l==r){t[x].init(r-l+1);return;}
        build(x<<1,l,mid,dep+1);
        build(x<<1|1,mid+1,r,dep+1);
    }

    inline void init(int x,int l,int r)
    {
        if(l==r){t[x].top=0;return;}
        t[x].clear();
        init(x<<1,l,mid);
        init(x<<1|1,mid+1,r);
    }

    inline void insert(int x,int l,int r,int p,const point &o,int dep=0)
    {
        if(l==r)
        {
            t[x].top=0;
            t[x].push_back(o);
            return;
        }

        if(p<=mid)
            insert(x<<1,l,mid,p,o,dep+1);
        else
            insert(x<<1|1,mid+1,r,p,o,dep+1);
        if(p==r)
            if(x!=lborder[dep] && !t[x-1].top)
                t[x-1].merge(t[(x-1)<<1],t[(x-1)<<1|1]);
    }

    inline ll query(int x,int l,int r,int dl,int dr,const point &o)
    {
        if(t[x].top && dl<=l && r<=dr)
            return t[x].query(o);
        ll ret=-1e18;
        if(dl<=mid)
            chkmax(ret,query(x<<1,l,mid,dl,dr,o));
        if(mid<dr)
            chkmax(ret,query(x<<1|1,mid+1,r,dl,dr,o));
        return ret;
    }

    inline void del(int x,int l,int r,int p,int dep=0)
    {
        t[x].clear();
        if(l==r)return;
        if(p<=mid)
            del(x<<1,l,mid,p,dep+1);
        else
            del(x<<1|1,mid+1,r,p,dep+1);
    }
}t;

int tp,m,top;

int main()
{
    t.build(1,1,n);
    tp=read();
    while(m=read())
    {
        top=0;t.init(1,1,n);
        int ty,l,r;point p;
        ll lans=0,ans;
        for(int i=1;i<=m;i++)
        {
            ty=read();
            if(ty==1)
            {
                p.x=read();p.y=read();
                t.insert(1,1,n,++top,p);
            }
            else if(ty==2)
                t.del(1,1,n,top--);
            else
            {
                l=read();r=read();
                p.x=read();p.y=read();
                ans=t.query(1,1,n,l,r,p);
                ans=(ans%md+md)%md;
                lans^=ans;
            }
        }

        printf("%lld\n",lans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值