UOJ191 Unknown

不想你离开。 提交于 2020-02-12 11:34:39

Unknown

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

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

2.删除\(S\)的末尾元素。

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

其中\(\times\)表示向量的叉积,\((x_1,y_1) \times (x_2,y_2) = x_1y_2-x_2y_1\)

题解

https://www.cnblogs.com/showson/p/5602460.html

使用线段树按下标维护凸包。那么这里有一个问题,如果按照传统的写法,合并一次的复杂度是与O(区间长度)的,这样会导致单次插入/删除的时间复杂度变为O(n),是不能接受的。

注意到与普通的线段树不一样的是,这个只会从后面添加元素,所以一个区间只有被填满了以后才需要询问,进而我们可以只在这个区间被填满了以后再合并,但是单次操作的最坏时间复杂度仍是O(n)。

考虑是什么样的操作使上面的复杂度变成O(n)的呢:在某个较长的区间的右端点附近来回插入删除,每当它满了就会合并一个,删除再插入一个又会满。

于是我们考虑,在这个区间刚满的时候不合并这个区间,而是等到同层的下一个区间也填满的时候再合并这个区间。

这样一旦一个结点花费O(L)的时间合并后,至少O(L)次操作才会使它再次合并,所以每层单个操作的均摊时间复杂度为O(1),共log层,单个操作的均摊时间复杂度为O(log n).

但是这样做的话,当一个结点被询问区间完全包含的时候我们并不一定直接使用这个结点的信息,因为它还没有合并。我们只能继续递归它的儿子,直到发现合并过的节点才能返回答案。

那么一共会用到多少个区间的信息呢?我们知道线段树区间查询会落在O(log n)个区间上,由于每层只有最后一个区间没有合并,所以这O(log n)个区间里只有最右边的区间最多会放在log个子区间上,其他的最多放在2个子区间上,所以总共仍然会落在O(log n)个区间上。

这个题需要在凸包上二分,所以单次询问时间复杂度是O(log n),总时间复杂度O(n log n),空间复杂度O(n log n).

struct point {int x,y;};
IN point operator-(CO point&a,CO point&b){
    return {a.x-b.x,a.y-b.y};
}
IN bool operator<(CO point&a,CO point&b){
    return a.x!=b.x?a.x<b.x:a.y<b.y;
}
IN int64 cross(CO point&a,CO point&b){
    return (int64)a.x*b.y-(int64)a.y*b.x;
}

struct convex{
    vector<point> ch;
    
    void push_back(CO point&p){
        int n=ch.size();
        for(;n>=2 and cross(p-ch[n-2],ch[n-1]-ch[n-2])<=0;--n) ch.pop_back();
        ch.push_back(p);
    }
    void merge(CO convex&a,CO convex&b){
        ch.clear();
        int na=a.ch.size(),nb=b.ch.size();
        int i=0,j=0;
        while(i<na or j<nb){
            if(j==nb or (i<na and a.ch[i]<b.ch[j])) push_back(a.ch[i++]);
            else push_back(b.ch[j++]);
        }
    }
    int64 query(CO point&p)CO{
        int l=0,r=ch.size()-1;
        while(l<r){
            int mid=(l+r)>>1;
            if(cross(p,ch[mid+1]-ch[mid])>0) l=mid+1;
            else r=mid;
        }
        return cross(p,ch[l]);
    }
};

CO int N=3e5+10;
convex tree[4*N];
int last[20];

#define mid ((l+r)>>1)
#define lc (x<<1)
#define rc (x<<1|1)
void build(int x,int l,int r){
    tree[x].ch.clear();
    if(l==r) return;
    build(lc,l,mid),build(rc,mid+1,r);
}
void insert(int x,int l,int r,int d,int p,CO point&v){
    if(r==p){
        int&y=last[d];
        if(y) tree[y].merge(tree[y<<1],tree[y<<1|1]);
        y=l==r?0:x;
    }
    if(l==r){
        tree[x].ch.clear(),tree[x].push_back(v);
        return;
    }
    if(p<=mid) insert(lc,l,mid,d+1,p,v);
    else insert(rc,mid+1,r,d+1,p,v);
}
void erase(int x,int l,int r,int d,int p){
    tree[x].ch.clear();
    if(l==r) return;
    if(last[d]==x) last[d]=0;
    if(p<=mid) erase(lc,l,mid,d+1,p);
    else erase(rc,mid+1,r,d+1,p);
}
int64 query(int x,int l,int r,int ql,int qr,CO point&v){
    if(ql<=l and r<=qr and tree[x].ch.size()) return tree[x].query(v);
    if(qr<=mid) return query(lc,l,mid,ql,qr,v);
    if(ql>mid) return query(rc,mid+1,r,ql,qr,v);
    return max(query(lc,l,mid,ql,qr,v),query(rc,mid+1,r,ql,qr,v));
}
#undef mid
#undef lc
#undef rc

CO int mod=998244353;
void real_main(int m){
    memset(last,0,sizeof last);
    int n=0,sz=min(m,(int)3e5);
    build(1,1,sz);
    int prt=0;
    while(m--){
        int opt=read<int>();
        if(opt==1){
            int x=read<int>(),y=read<int>();
            insert(1,1,sz,0,++n,{x,y});
        }
        else if(opt==2) erase(1,1,sz,0,n--);
        else{
            int l=read<int>(),r=read<int>(),x=read<int>(),y=read<int>();
            int64 ans=query(1,1,sz,l,r,{x,y});
            prt^=(ans%mod+mod)%mod;
        }
    }
    printf("%d\n",prt);
}
int main(){
    read<int>();
    for(int m;read(m);) real_main(m);
    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!