值域倍增分块&底层分块&题解:[Ynoi2007] rgxsxrs
题解
这种
就是把值域分成
然后我们对每一个块都开一个线段树维护序列。
对于块
线段树维护的是区间
那显然查询的时候对
看修改,对于块
-
:此时线段树上 中所有数都要 (没有贡献的位置当然不用)。
因为 会在线段树上拆成 个子区间,我们就对其中一个子区间 考虑。
如果 的区间最小值 之后不再属于 ,他会掉到下面的块中。
因为总共只有 个块,所以一个位置只会掉 次。
所以我们直接暴力 把最小值从当前线段树删去(可以用线段树二分找),加入对应块的线段树。
这个操作的总复杂度是 。
对于剩下的那些数直接打上区间减 tag 即可,总复杂度就是普通线段树操作的复杂度 。
Tip: 当然不一定只有最小值要掉,次小值可能也会,次次小值可能也会,得不断删最小值直到最小值 之后不会掉下去。 -
:啥都不用做。 -
:此时 中一些比较大的数需要 。
但因为 ,所以减完之后至少减半,所以只会减半 次,且一定会掉到下面的块,同理暴力修改的复杂度是 。
所以直接暴力找到所有 的叶子并修改即可。
总时间复杂度是
如果就这样的话,这题还不是很毒瘤,也不是很难写。
但这是 ynoi,你会愉快地 MLE。
卡空间的方法是底层分块。
其实说人话就是当线段树某个节点代表的区间长度
你稍微分析一下就会发现区间操作最多只会暴力
所以此时常规线段树操作的单次复杂度变成
而对于
可以认为是先花
每棵线段树的节点数就是
理论上取
到这里两个 trick 就讲完了,只想学这两个 trick 的可以不用往下看了。
当然 ynoi 不卡常是不可能的。
主要就是改两个块长。
倍增值域分块的底数取
如果我们按照
会分成
所以情况
对应的如果要让空间为
虽然当
我取了
当然,代码最终解释权归卡常所有。
code
#include<bits/stdc++.h>
#define LL long long
#define PIIII pair<pair<LL,int>,pair<int,int>>
#define PIII pair<LL,pair<int,int>>
#define fi first
#define se second
#define ls(p) t[p].ls
#define rs(p) t[p].rs
using namespace std;
const int N=5e5+5,K=32,B=40,V=1e9,inf=2*V,N2=240000,mod=(1<<20);
inline int read(){
int w=1,s=0;
char c=getchar();
for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());
for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());
return w*s;
}
int n,T,a[N],ll,rr,xx,ID[N],tmp;
int L[35],R[35],CNT,rt[35];
int tot;
int Get_id(int x){return upper_bound(L,L+CNT+1,x)-L-1;} //返回值 x 所在值域块编号
struct node{
int l,r,ls,rs,maxn,ming,cnt,add; //注意 add 是不用 LL 的,不会超过 max(a[i])
LL sum;
void tag(int d){
add+=d; sum+=1ll*cnt*d; //注意不是 (r-l+1)*d
if(cnt) ming+=d; maxn+=d;
}
}t[N2];
void Tag(int p,int id,int l,int r,int d){ //把对应块中的所有数 +d
LL sum=0;
int cnt=0,maxn=0,ming=inf;
for(int i=l;i<=r;i++){
if(ID[i]==id){
a[i]+=d;
ID[i]=Get_id(a[i]);
cnt++,sum+=a[i],maxn=max(maxn,a[i]),ming=min(ming,a[i]);
}
}
t[p].sum=sum,t[p].cnt=cnt,t[p].maxn=maxn,t[p].ming=ming;
}
void pushup(int p){
t[p].sum=t[ls(p)].sum+t[rs(p)].sum;
t[p].cnt=t[ls(p)].cnt+t[rs(p)].cnt;
t[p].ming=min(t[ls(p)].ming,t[rs(p)].ming);
t[p].maxn=max(t[ls(p)].maxn,t[rs(p)].maxn);
}
void pushdown(int id,int p){ //根据我们打懒标记的规则会发现,pushdown 完之后一定不会出现有元素掉到下一个块的情况
if(t[p].add){
if(t[ls(p)].r-t[ls(p)].l+1<=B) Tag(ls(p),id,t[ls(p)].l,t[ls(p)].r,t[p].add);
else t[ls(p)].tag(t[p].add);
if(t[rs(p)].r-t[rs(p)].l+1<=B) Tag(rs(p),id,t[rs(p)].l,t[rs(p)].r,t[p].add);
else t[rs(p)].tag(t[p].add);
t[p].add=0;
}
}
void query(int p,int id,int l,int r){ //暴力查询区间的sum,min,max
LL sum=0;
int cnt=0,maxn=0,ming=inf;
for(int i=l;i<=r;i++) if(ID[i]==id) cnt++,sum+=a[i],maxn=max(maxn,a[i]),ming=min(ming,a[i]);
t[p].sum=sum,t[p].cnt=cnt,t[p].maxn=maxn,t[p].ming=ming;
}
void Insert(int id,int p,int x){ //将某个位置插入线段树
if(t[p].r-t[p].l+1<=B){
a[x]-=xx; //在这个地方减掉。
ID[x]=Get_id(a[x]);
query(p,id,t[p].l,t[p].r);
return;
}
pushdown(id,p);
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) Insert(id,t[p].ls,x);
else Insert(id,t[p].rs,x);
pushup(p);
}
void modify(int p,int id,int l,int r){ //暴力把区间 >x 的 -x 并求答案
LL sum=0;
int cnt=0,maxn=0,ming=inf;
for(int i=l;i<=r;i++){
if(ID[i]==id){
if(ll<=i && i<=rr && a[i]>xx) a[i]-=xx,ID[i]=Get_id(a[i]); //注意只有在操作范围内的才改
if(ID[i]!=id){ //掉到下一个块的话要先把他加回来,不然在 insert 中会再减一次
tmp=ID[i];
a[i]+=xx;
ID[i]=Get_id(a[i]);
Insert(tmp,rt[tmp],i);
}
else cnt++,sum+=a[i],maxn=max(maxn,a[i]),ming=min(ming,a[i]);
}
}
t[p].sum=sum,t[p].cnt=cnt,t[p].maxn=maxn,t[p].ming=ming;
}
void erase(int p,int id,int l,int r,int val){ //把区间 [l,r] 中一个值为 val 的数 -x,并从当前线段树删掉
LL sum=0;
int cnt=0,maxn=0,ming=inf;
bool flag=false; //只能删一个
for(int i=l;i<=r;i++){
if(ID[i]==id){
if(!flag && a[i]==val){ //注意不要直接在这个地方把他 -x 不然在 insert 中 pushdown 可能会让他多减一些东西
flag=true;
tmp=Get_id(a[i]-xx);
Insert(tmp,rt[tmp],i); //掉到下一个块
}
else cnt++,sum+=a[i],maxn=max(maxn,a[i]),ming=min(ming,a[i]);
}
}
t[p].sum=sum,t[p].cnt=cnt,t[p].maxn=maxn,t[p].ming=ming;
}
int build(int id,int l,int r){ //建树
int p=++tot;
t[p].l=l,t[p].r=r;
if(r-l+1<=B){
query(p,id,t[p].l,t[p].r);
return p;
}
int mid=(l+r)>>1;
t[p].ls=build(id,l,mid);
t[p].rs=build(id,mid+1,r);
pushup(p);
return p;
}
void find(int id,int p,int val){
if(t[p].r-t[p].l+1<=B){
erase(p,id,t[p].l,t[p].r,val); //注意这个地方只能把最小值改掉,而不能直接把所有 >x 的全 -x,不然后面打懒标记就重复减了
return;
}
pushdown(id,p);
if(t[ls(p)].ming==val) find(id,t[p].ls,val);
else find(id,t[p].rs,val);
pushup(p);
}
void change1(int id,int p){ //情况 1 的区间修改
if(t[p].cnt==0) return; //空的,没有这个块中的值
if(t[p].r-t[p].l+1<=B){ //直接暴力
modify(p,id,t[p].l,t[p].r);
return;
}
if(ll<=t[p].l&&t[p].r<=rr){
while(t[p].cnt && t[p].ming-xx<L[id]) find(id,p,t[p].ming);
t[p].tag(-xx);
return;
}
pushdown(id,p);
int mid=(t[p].l+t[p].r)>>1;
if(ll<=mid) change1(id,t[p].ls);
if(rr>mid) change1(id,t[p].rs);
pushup(p);
}
void change2(int id,int p){ //情况 3 的区间修改,这个就很暴力了,如果当前区间的最大值 >xx 就直接往下递归,也不需要拆成 log 个子区间
if(t[p].cnt==0 || t[p].maxn<=xx) return;
if(t[p].r-t[p].l+1<=B){ //直接暴力
modify(p,id,t[p].l,t[p].r);
return;
}
pushdown(id,p);
int mid=(t[p].l+t[p].r)>>1;
if(ll<=mid) change2(id,t[p].ls);
if(rr>mid) change2(id,t[p].rs);
pushup(p);
}
PIII merge(PIII x,PIII y){return {x.fi+y.fi,{max(x.se.fi,y.se.fi),min(x.se.se,y.se.se)}};}
PIII ask(int id,int p){
if(t[p].r-t[p].l+1<=B){
query(0,id,max(ll,t[p].l),min(rr,t[p].r));
return {t[0].sum,{t[0].maxn,t[0].ming}};
}
if(ll<=t[p].l&&t[p].r<=rr) return {t[p].sum,{t[p].maxn,t[p].ming}};
pushdown(id,p);
int mid=(t[p].l+t[p].r)>>1;
PIII res={0,{0,inf}};
if(ll<=mid) res=merge(res,ask(id,t[p].ls));
if(rr>mid) res=merge(res,ask(id,t[p].rs));
return res;
}
void Init(){
L[0]=1,R[0]=1;
while(true){
++CNT;
L[CNT]=R[CNT-1]+1,R[CNT]=min(1ll*V,1ll*L[CNT]*K-1ll);
if(R[CNT]==V){break;}
}
for(int i=1;i<=n;i++) ID[i]=Get_id(a[i]);
for(int i=0;i<=CNT;i++) rt[i]=build(i,1,n);
}
signed main(){
n=read(),T=read();
for(int i=1;i<=n;i++) a[i]=read();
Init();
int lstans=0;
while(T--){
int op=read();
ll=read()^lstans,rr=read()^lstans;
if(op==1){
xx=read()^lstans;
for(int i=0;i<=CNT;i++){
if(xx<L[i]) change1(i,rt[i]);
else if(xx>=L[i]&&xx<=R[i]) change2(i,rt[i]); //注意是 <= 不是 < 因为这里是闭区间
}
}
else{
PIII ans={0,{0,inf}};
for(int i=0;i<=CNT;i++){ans=merge(ans,ask(i,rt[i]));}
lstans=ans.fi%mod;
printf("%lld %d %d\n",ans.fi,ans.se.se,ans.se.fi);
}
}
return 0;
}
闲话
这题之后我写过的代码长度的记录就不是 8.6K 了,而是
说明:我比较喜欢写注释,所以我贴代码的时候去掉了大量影响观感的注释,所以代码本身其实没有这么长。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~