P8524[Ynoi2078](区间右移复制求和)

· · 题解

Part1 题意简述:

三个操作:区间按位右移,复制,求和。

### Part2 前置知识: [可持久化 WBLT](/blog/cjxdesmall/good-merge-wblt);[花神游历各国](/problem/P4145)。 ### Part3 正题: 如果没有复制操作,就可以暴力右移非零数,复杂度 $O(n\log_2n\log_2C)$,但有复制操作,就会影响势能,时间退化成 $O(nm)$。 使用可持久化平衡树维护复制操作,依旧考虑每一个数最多右移 $\log_2C$ 次,这样可以树上每一个节点存储右移 $[0,\log_2C]\cap Z$ 次的结果,而每一次操作会多出 $O(\log_2n)$ 个节点,这样空间复杂度是 $O(m\log_2n\log_2C)$ 的,不能通过。 如果每隔 $\frac{m}{\log_2n}$ 次操作就搜出所有节点,然后重构,就可以做到空间 $O(n\log_2C)$,但是时间有 $O((n+m)\log_2n\log_2C)$,到这里,如果实现得好一些已经可以通过了。 这里有一个小问题,就是本题给的时间和空间极度不平衡,光建立平衡树就必须 $n\log_2C$ 个 `long long`,而这样的空间已经达到了 $750MB$,显然开不下。 考虑在建树时底层分块,如果区间长度少于 $\log_2C$,就将整个区间当作一个节点,这样一棵全新的树空间是 $O(n)$ 的。 一棵可爱的 $\text{WBLT}$: ```cpp ul sm[M][33],ans; D n,q,a[N],t[M][2]; #define ls t[x][0] #define rs t[x][1] D g[M],cnt,F[M],ft; D sz[M],L[M],b[N],rt; inline void cp(D &x){ if(F[x]==ft)return; F[++cnt]=ft;L[cnt]=L[x]; g[cnt]=g[x],sz[cnt]=sz[x]; t[cnt][0]=ls,t[cnt][1]=rs; for(D i=0;i<=30;++i) sm[cnt][i]=sm[x][i]; x=cnt; }inline void add(D &x,D ta){ cp(x),g[x]=min(g[x]+ta,30u); } inline void pp(D x){ sz[x]=sz[ls]+sz[rs]; L[x]=L[ls]+L[rs]; for(D i=0;i<=30;++i){ sm[x][i]=0; if(i+g[ls]<=30)sm[x][i]+=sm[ls][i+g[ls]]; if(i+g[rs]<=30)sm[x][i]+=sm[rs][i+g[rs]]; } }inline void pd(D &x){ if(g[x]&&sz[x]>1){ cp(x),add(ls,g[x]); add(rs,g[x]); for(D i=30-g[x];~i;--i) sm[x][i]=sm[x][i+g[x]]; for(D i=30-g[x]+1;i<=30;++i) sm[x][i]=0; g[x]=0; } } D spt(D x,D l,D r){ if(!x||l>r)return 0; if(l==1&&r==L[x])return x; if(sz[x]==1){ g[++cnt]=g[x],L[cnt]=r-l+1,sz[cnt]=1; t[cnt][0]=ls+l-1,t[cnt][1]=ls+r-1; F[x=cnt]=ft;D i,j; for(i=0;i<=30;++i) for(sm[x][i]=0,j=ls;j<=rs;++j) sm[x][i]+=a[j]>>i; return x; }else{ cp(x),pd(x); if(r<=L[ls])return spt(ls,l,r); else if(l>L[ls])return spt(rs,l-L[ls],r-L[ls]); else{ rs=spt(rs,1,r-L[ls]),ls=spt(ls,l,L[ls]); pp(x);return x; } }exit(100000);return 1e9; } D build(D l,D r){ D x=++cnt;F[x]=ft,g[x]=0,L[x]=r-l+1; if(r-l<30){ ls=l,rs=r,sz[x]=1;D i,j; for(i=0;i<=30;++i) for(sm[x][i]=0,j=l;j<=r;++j) sm[x][i]+=a[j]>>i; }else{ D md=l+r>>1;ls=build(l,md); rs=build(md+1,r);pp(x); }return x; } void dfs(D x,D dat=0){ dat=min(dat+g[x],30u); if(sz[x]>1){ dfs(ls,dat),dfs(rs,dat); }else{ for(D i=ls;i<=rs;++i) b[++n]=a[i]>>dat; } } D mg(D L,D R){ if(!L||!R)return L|R; D x=sz[L]>sz[R]?L:R,sm=sz[L]+sz[R]; if(sz[x]<ra*sm){ g[x=++cnt]=0,F[x]=ft; ls=L,rs=R,pp(x); return x; }if(x==L){ cp(x),pd(x); if(sz[ls]>af*sm){ rs=mg(rs,R),pp(x),g[x]=0; return x; }else{ D y=rs;cp(y),pd(y); return mg(mg(ls,t[y][0]),mg(t[y][1],R)); } }else{ cp(x),pd(x); if(sz[rs]>af*sm){ ls=mg(L,ls),pp(x),g[x]=0; return x; }else{ D y=ls;cp(y),pd(y); return mg(mg(L,t[y][0]),mg(t[y][1],rs)); } }exit(1000000);return 1e9; } ``` ### Part4 关于常数 显然在没什么人提交时我一发得到了最优解 $(2022.9.29)$,时间最快 $1.70min$,空间最小 $160MB$,由此可见,常数优秀的 $\text{WBLT}$ 让我们不再需要卡常。时限太变态了,缩到 $30s$ 怎么样?或者把空间放到 $200MB$?不然这就是一个完全不卡常的 $\text{Ynoi}$ 了。 ### Part5 后记 这篇文章只是抛砖引玉,作者听说有空间线性的做法,以及可以分裂合并的 $\text{AVL}$ 树,希望大家多多分享。