GMOJ 3304 Theresa与数据结构 题解


我爱死这个出题人了

简要の题意

裸的四维偏序。

\(n\le 100000\)

思路

众所周知,这题显然可以写 \(O(n\log^3n)\) 的算法。

比如树套树套树,或者 cdq 分治套树套树,或者 cdq 分治套 cdq 分治套树……

除非真的是 KDT 的题目,不然在不卡空间的情况下 KDT 真跑不过这类东西

那么我们算一下:

\(n\log^3n=100000\times\log^3 100000\thickapprox 458226982.3989921344\)

MMP 出题人这 NM 怎么过啊

本着多少能拿分的想法写了 cdq 分治套树状数组套线段树,调了 4 个小时,事实证明能拿暴力分。

WDNMD

然后据说 KDT 并不会更短或者更容易过去并且我已经写了接近 5K 了,决定改成 cdq 分治套 cdq 分治套树状数组。

又调了 2h 过了,开 O2 可以跑进一秒。

然后又卡了卡常,不开 O 也可以跑进一秒。

CODE

剩下的大部分的细节丢进了注释,应该还算详细。

建议丢进 Astyle 或者 Clang Format 之后食用。

#pragma GCC optimize(2)
#include
#include
#include
#define reg register
#define ll long long
using namespace std;
const int N=105000,FSIZE=1<<22,NN=N+N+N;
int cnt=1,cntx,cnty,cntz,st[N],mx,my,mz,*lsx[NN],*lsy[NN],*lsz[NN],*tmp[NN];
struct ques{
    int mode,x,yl,yr,dy,zl,zr,ans;
}e[NN],*dvd[NN],*dl[NN],*dr[NN],p[NN],*d2[NN],*l2[NN],*r2[NN],*num[NN];
struct SToFT{    //这里曾经是 Segment Tree On Fenwick Tree 的意思
    #define lowb(x)((x)&-(x))
    int a[NN];
    void modify(reg int x,reg int c){
        for(;x<=mz;x+=lowb(x)) a[x]+=c;
    }
    int sum(reg int x){
        reg int re=0;
        for(;x;x-=lowb(x)) re+=a[x];
        return(re);
    }
    int query(reg int zl,reg int zr){
        return(sum(zr)-sum(zl-1));
    }
}t;
char BuF[FSIZE],*InF=BuF;
int read(){   //读入的一部分
    reg int x=0;
    for(;47>*InF||*InF>58;++InF);
    for(;47<*InF&&*InF<58;x=x*10+(*InF++^48));
    return(x);
}
void sort(reg int **ls,reg int cnt,reg int t,reg int z){
    //基数排序,不知道为什么松式排序反而慢一些
    int w[65536];
    memset(w,0,sizeof(w));
    for(reg int **i=ls,**r=ls+cnt;i>z]);
    for(reg int *i=w,*r=w+65535;i=ls;--i) tmp[--w[(**i&t)>>z]]=*i;
    for(reg int i=0;i>1,cl=0,cr=0;
        for(reg int i=ql;i<=qr;++i){
            ques &now=*d2[i];    //使用指针作为操作顺序的存放方式,避免结构体复制影响效率
            if(now.mode==1){
                if(l==r||now.yl>mid){    //l==r 时全部合法
                    now.ans+=t.query(now.zl,now.zr);
                }
            }else if(l==r||now.yl<=mid){
                t.modify(now.zl,now.mode?-1:1);
            }
            if(now.yl>mid) r2[cr++]=&now;
            else l2[cl++]=&now;
        }
        for(reg int i=ql;i<=qr;++i){
            //把树状数组的更改还原,使用时间戳清空也是可以的,但略慢(查询太多)
            ques &now=*d2[i];
            if(now.mode!=1&&(l==r||now.yl<=mid)){
                t.modify(now.zl,now.mode?1:-1);
            }
        }
        for(reg int i=ql;i>1,cl=0,cr=0,cnt=0;
        for(reg int i=ql;i<=qr;++i){
            ques &now=*dvd[i];    //使用指针作为操作顺序的存放方式,避免结构体复制影响效率
            if(now.mode==1){
                if(l==r||now.x>mid){
                    ++cnt;d2[cnt]=&(p[cnt]=now);p[cnt].ans=0;p[cnt].yl=p[cnt].dy;
                    num[cnt]=&now;
                    ++cnt;d2[cnt]=&(p[cnt]=now);p[cnt].ans=0;p[cnt].yl=p[cnt].yr;
                    //创建两个二次询问,容斥出一次查询,ans 注意清空(虽然不清好像也没问题)
                    //注意最后的细微差别
                }
            }else if(l==r||now.x<=mid){
                p[++cnt]=now;
                d2[cnt]=&p[cnt];
            }
            if(now.x>mid) dr[cr++]=&now;
            else dl[cl++]=&now;
        }
        cdq2(1,my,1,cnt);  //二次 CDQ 出对于在 mid 右的询问包含在几个 mid 左的点
        for(int i=1;i<=cnt;++i){    //找到每个二次询问,把对应的一次询问的答案更新
            if(p[i].mode==1){
                num[i]->ans+=p[i+1].ans-p[i].ans;
                ++i;
            }
        }
        for(reg int i=ql;i32;++InF);   //读入的一部分
        }else{
            read(e[cnt]);
            if(ch=='Q'){
                r=read();
                e[cnt].dy=e[cnt].yl-1;    //求出 y-1 作为容斥的一部分(离散化用)
                e[cnt].yr=e[cnt].yl+r;    //求出 y+r 作为容斥的一部分(离散化用)
                e[cnt].zr=e[cnt].zl+r;    //求出 z+r 作为容斥的一部分(离散化用)
                e[cnt].mode=1;
                e[cnt+1]=e[cnt];
                e[cnt+1].x+=r;    //求出 x+r 作为容斥的一部分(离散化用)
                --e[cnt].x;    //求出 x-1 作为容斥的一部分(离散化用)
                lsy[cnty++]=&e[cnt].dy;lsy[cnty++]=&e[cnt+1].dy;
                //将 cnt 与 cnt+1 的 y-1 单独放入 y 的离散化数组
                push(e[cnt].x,e[cnt].yl,e[cnt].zl);
                //将 cnt 的 x yl zl 分别放入离散化数组
                lsy[cnty++]=&e[cnt].yr;lsz[cntz++]=&e[cnt].zr;
                //将 cnt 的 y+r 和 z+r 单独放入离散化数组,cnt+1 的等一会处理
                ++cnt;    //注意这里 +1
            }else{
                st[++st[0]]=cnt;
            }
        }
        push(e[cnt].x,e[cnt].yl,e[cnt].zl);
        //将 cnt 的 x yl zl 分别放入离散化数组
        //注意在 if 外,这样可以把 CANCEL 操作也放入
    }
    lsh(lsx,mx,cntx);lsh(lsy,my,cnty);lsh(lsz,mz,cntz);
    //离散化
    for(reg int i=1;i