题解:P6109 [Ynoi2009] rprmq1

Officer_Xia_ZhuRen

2025-01-09 20:29:28

Solution

P6109 [Ynoi2009] rprmq1 题解

猫树分治 + 区间历史最值。

没找到猫树分治的模板只能过来写这个了。

猫树分治,是线段树分治的一种,类似于整体二分和线段树分治的结合,但每次把越过中点的询问处理并删除,而不是到底在处理,这里需要注意与 mid 或者 (mid+1) 相等也是中点。

注意到每次询问的矩形是 4-Side 的,也就是有 4 条边的。

参考小学奥数,老爷爷喜欢把篱笆围在靠墙的地方,这提醒我们把矩形拆分成两个由中点延伸的部分,这样可以用两次扫面线扫描线段树历史最值取最大值得到答案。

就像这样。

如果你不会线段树历史最值,参考: P4314 CPU 监控。

所以考虑一种及其暴力的情况:我们每次在线段树上插入矩形,然后每次把矩形处理成扫描线线段加入。

注意几个细节。

$2.$ 注意开 `long long`。 只要在递归子节点的时候把区间都有的矩形加上,每次扫描线后撤销操作,递归推出撤销操作即可。 看一眼,**本题不卡常**!高兴地交上去,发现 **MLE** 了。 我们发现每次撤销操作的空间复杂度是 $\mathcal{O}(mlog^{2}n)$ 的,严重超出限制。 考虑用更好的撤销方法,发现区间历史最值线段树不易撤销,所以对全局加上一个极大值,后面再减去加上的和。 当然还有另一种做法,每次递归子区间的时候把另一半先算上,递归完撤销递归另一半,但细节过多,并未采用。 更多细节详见代码。 **为了防抄把历史最值线段树删掉了,且函数中有几处错误。** 码风**不**优美,见谅。 ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll1; template<typename T> void in(T &n){n=0;char c=getchar();bool flag=0;for(;c<'0'||c>'9';c=getchar()) if (c=='-') flag=1;for(;c>='0'&&c<='9';c=getchar())(n*=10)+=(c^48);if(flag)n=-n;} int wlsk[45];int wltp; template<typename T> void out(T n,char c=0){if(n==0){putchar('0');if(c)putchar(c);return;}if(n<0)putchar('-'),n=-n;while(n) wlsk[++wltp]=(n%10),n/=10;while(wltp)putchar(wlsk[wltp--]^48);if(c)putchar(c);} const int N=5e4+5; const int M=5e5+5; const ll1 inf=5e4*(long long)INT_MAX; namespace Sgt{//历史最值线段树,封namespace里。 } int n,m,q; //按照一维坐标分治 struct Query{int xl,xr,yl,yr,id;}qs[M]; int totq,tota; struct Square{int lx,rx,ly,ry,val;}; struct Node{ int l,r; vector<Square>sqr;//每个的矩形加 vector<Square>tag;//打标记的矩形加(不与矩形重叠) }t[N*4]; #define ls (o<<1) #define rs (ls|1) #define mid ((l+r)>>1) void build(int o,int l,int r){ t[o]=(Node){l,r};if(l==r)return; build(ls,l,mid),build(rs,mid+1,r); } void insert(int o,int lt,int rt,Square x){//孩子们,其实这和树套树真没区别 int l=t[o].l,r=t[o].r; if(l>=lt&&r<=rt){t[o].tag.push_back(x);return;} t[o].sqr.push_back(x); if(lt<=mid)insert(ls,lt,rt,x); if(rt>mid)insert(rs,lt,rt,x); } void init(){ in(n),in(m),in(q); int xl,yl,xr,yr,val; build(1,1,n); Sgt::build(1,1,n); for(int i=1;i<=m;i++){ in(xl),in(yl),in(xr),in(yr),in(val); insert(1,xl,xr,(Square){xl,xr,yl,yr,val}); } for(int i=1;i<=q;i++){ in(xl),in(yl),in(xr),in(yr); qs[++totq]=(Query){xl,xr,yl,yr,i}; } } struct Seg{ int x,l,r,val; bool op;//0修1问 //注意同值情况的加入顺序,先减再加,防止历史最值溢出 }sgl[M*4],sgr[M*4];//存储当前猫树分治 int lts=0,rts=0; bool CMPL(Seg x,Seg y){//倒序 if(x.x!=y.x)return x.x>y.x; if(x.op!=y.op)return x.op<y.op; return x.val<y.val; } bool CMPR(Seg x,Seg y){ if(x.x!=y.x)return x.x<y.x; if(x.op!=y.op)return x.op<y.op; return x.val<y.val; } void Devide(int l,int r,Square sq){//拆分矩形加 if(sq.rx<=mid){//在左边的询问 sgl[++lts]=(Seg){sq.rx,sq.ly,sq.ry,sq.val,0}; sgl[++lts]=(Seg){sq.lx-1,sq.ly,sq.ry,-sq.val,0}; }else if(sq.lx>mid){//右边 sgr[++rts]=(Seg){sq.lx,sq.ly,sq.ry,sq.val,0}; sgr[++rts]=(Seg){sq.rx+1,sq.ly,sq.ry,-sq.val,0}; }else{//两边都有 sgl[++lts]=(Seg){mid,sq.ly,sq.ry,sq.val,0}; sgl[++lts]=(Seg){sq.lx-1,sq.ly,sq.ry,-sq.val,0}; sgr[++rts]=(Seg){mid+1,sq.ly,sq.ry,sq.val,0}; sgr[++rts]=(Seg){sq.rx+1,sq.ly,sq.ry,-sq.val,0}; } } //其中1%数据,为样例1 ll1 allinf=0;//遗忘操作加 void DateBack(){//遗忘操作,加一个极大值再减去即可。 //这里取值域上限,用long long即可 Sgt::add(1,1,n,inf); allinf+=inf; } ll1 Ans[M]; void Left(){ if(!lts)return; sort(sgl+1,sgl+1+lts,CMPL); int l,r,w; for(int i=1;i<=lts;i++){ l=sgl[i].l,r=sgl[i].r; w=sgl[i].val; if(sgl[i].op==0){ Sgt::add(1,l,r,w); }else{ Ans[w]=max(Ans[w],Sgt::query(1,l,r)-allinf); } } } void Right(){ if(!rts)return; sort(sgr+1,sgr+1+rts,CMPR); int l,r,w; for(int i=1;i<=rts;i++){ l=sgr[i].l,r=sgr[i].r; w=sgr[i].val; if(sgr[i].op==0){ Sgt::add(1,l,r,w); }else{ Ans[w]=max(Ans[w],Sgt::query(1,l,r)-allinf); } } } Query ql[M],qr[M]; void solve(int o,int lt,int rt){ if(lt>rt)return; int l=t[o].l,r=t[o].r; for(auto sq:t[o].tag)Sgt::add(1,sq.ly,sq.ry,sq.val); lts=rts=0;int cl=0,cr=0;//左边的,右边的询问 for(auto sq:t[o].sqr)Devide(l,l,sq);//放入当前这轮的询问矩形 //当前线段树分治存储的矩形加,构成猫树分治,考虑矩形。 for(int L,R,i=lt;i<=rt;i++){//处理询问 L=qs[i].xl,R=qs[i].xr; if((R<mid)||(L>mid+1)){ if(R<mid)ql[++cl]=qs[i]; else qr[++cr]=qs[i];//下一层解决! continue; } if(R==mid){//左边的询问 sgl[++lts]=(Seg){L,qs[i].yl,qs[i].yr,qs[i].id,1}; }else if(L==mid+1){//右边的询问 sgr[++rts]=(Seg){R,qs[i].yl,qs[i].yr,qs[i].id,1}; }else{//都有 sgl[++lts]=(Seg){L,qs[i].yl,qs[i].yr,qs[i].id,1}; sgr[++rts]=(Seg){R,qs[i].yl,qs[i].yr,qs[i].id,1}; } } for(int i=lt;i<lt+cl;i++)qs[i]=ql[i-lt+1]; for(int i=rt-cr+1;i<=rt;i++)qs[i]=qr[i-rt+cr]; Right();//做右边3side矩形 DateBack();//遗忘历史最值操作。 Left();//做左边的3side矩形 DateBack();//遗忘历史最值操作。 if(l!=r)solve(ls,lt,lt+cl-1),solve(rs,rt-cr+1,rt);//递归子结点 for(auto sq:t[o].tag)Sgt::add(1,sq.ly,sq.ry,-sq.val);//减去标记 DateBack();//遗忘历史最值操作。 } void work(){ solve(1,1,q); for(int i=1;i<=q;i++){ out(Ans[i],'\n'); } } signed main(){ init(); work(); return 0; } ```