定长分块
被 NOIP2025 T4 创飞后紧急学习这个 trick。
简介
处理限制区间长度在某个范围的区间问题时,可以考虑定长分块这个 trick。
I.[EC Final 2021] Vacation
题意:给定常数 \(c\) 和序列 \(a\),单点修改,区间查询 \([L,R]\) 内长度不超过 \(c\) 的最大子段和,答案和 \(0\) 取 \(\max\)。
把原序列以 \(c\) 为块长分块,那显然合法子段不可能跨过整块,对于区间询问,可以把答案拆成四部分:整块内,散块内,两个整块之间,散块和他旁边的块之间。
- 整块内和散块内:块内的区间一定合法,对每个块开一棵线段树 T1 维护块内最大子段和,询问的时候需要查询连续的若干整块的块内最大子段和,再开一棵叶子节点表示一个整块的线段树 T2 维护区间最大值即可。修改的时候先在 T1 上修改它所在块的线段树,再在 T2 上修改它所在块的值即可,全都是单点修,区间查。
- 整块和整块之间:记 \(pre_i/suf_i\) 表示 \(i\) 到它所在块的左端点/右端点的区间和(或者说块内前缀和/后缀和)。一组跨块的 \(l,r\) 需要满足 \(r-l+1\le c\),贡献为 \(suf_l+pre_r\)。改写一下限制变成 \(r-c<l\),显然 \(r-c\) 一定和 \(l\) 在一个块内,那么考虑设 \(x_i=suf_i,y_i=pre_{i+c}\) 问题变成在整块内求点对 \((i,j)\) 满足 \(i<j\) 使得 \(y_i+x_j\) 最大,可以用线段树轻松维护。对每个块开一棵线段树 T3,每个节点维护 \(\max(x),\max(y),\max(y_i+x_j)\) 即可。同样的询问需要查询连续的若干整块的最大值,再开一棵线段树 T4 维护即可。修改是在 T3 上区间加(注意一次修改会影响到两个块),然后在 T4 上单点修。T3 可以使用懒标记。
- 散块和它旁边的块之间:如果整个询问就在一个块中显然不需要考虑这个情况,否则问题形如给定两个相邻的长度 \(\le c\) 的区间 \([A,B],[C,D]\),求 \(\max_{l\in [A,B],r\in [C,D],r-l+1\le c}(suf_l+pre_r)\),类比整块和整块的思路,把 \([C,D]\) 的下标全体减 \(c\),变成 \([C',D']\),求 \(\max_{j\in [A,B],i\in [C',D'],i<j}(y_i+x_j)\),然后分类讨论:
- 如果 \(D'<A\):那么两部分独立,在 T3 上查询两部分 \(x/y\) 的最大值即可。
- 否则继续分讨,如果 \(i\in [C',A)\),那么两部分独立;不然 \(i\in [A,D']\),如果 \(j\in [A,D']\) 在 T3 上查询区间 \([A,D']\) 的 \(\max(y_i+x_j)\) 即可,否则 \(j\in (D',B]\),两部分还是独立。
复杂度 \(O(n\log n)\)。
code
有点长,但不是很难写。
点击查看代码
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define LL long long
#define ls(p) t[p].ls
#define rs(p) t[p].rs
using namespace std;
const int N=2e5+5;
const LL inf=1e15;
struct Node1{
LL sum,pre,suf,maxn;
void init(int x){
sum=x;
pre=suf=maxn=max(x,0);
}
};
Node1 operator + (const Node1 &L,const Node1 &R){
Node1 res;
res.sum=L.sum+R.sum;
res.pre=max(L.pre,L.sum+R.pre);
res.suf=max(R.suf,R.sum+L.suf);
res.maxn=max({L.maxn,R.maxn,L.suf+R.pre});
return res;
}
struct Node2{ LL x,y,xy; }; //x:B,y:A,xy:B+A
Node2 operator + (const Node2 &L,const Node2 &R){ return {max(L.x,R.x),max(L.y,R.y),max({L.xy,R.xy,L.x+R.y})}; }
int n,T,c,a[N],t,L[N],R[N],id[N];
LL s[N],A[N],B[N];
struct SegmentTree1{
int tot,rt[N];
struct node{
int l,r,ls,rs;
Node1 val;
}t[N<<2];
LL Val(int p){ return t[rt[p]].val.maxn; }
int New(int l,int r){
++tot;
t[tot].l=l,t[tot].r=r,t[tot].ls=t[tot].rs=0;
t[tot].val={0,0,0,0};
return tot;
}
void pushup(int p){ t[p].val=t[ls(p)].val+t[rs(p)].val; }
int build(int l,int r){
int p=New(l,r);
if(l==r) return t[p].val.init(a[l]),p;
int mid=(l+r)>>1;
ls(p)=build(l,mid),rs(p)=build(mid+1,r);
return pushup(p),p;
}
void modify(int p,int x,int val){
if(t[p].l==t[p].r) return t[p].val.init(val),void();
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) modify(ls(p),x,val);
else modify(rs(p),x,val);
pushup(p);
}
Node1 ask(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].val;
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) return ask(ls(p),l,r);
else if(l>mid) return ask(rs(p),l,r);
else return ask(ls(p),l,r)+ask(rs(p),l,r);
}
}Seg1;
struct SegmentTree2{
struct node{
int l,r;
LL maxn;
}t[N<<2];
void pushup(int p){
t[p].maxn=max(t[p<<1].maxn,t[p<<1|1].maxn);
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r) return t[p].maxn=Seg1.Val(l),void();
int mid=(l+r)>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
pushup(p);
}
void modify(int p,int x,LL val){
if(t[p].l==t[p].r) return t[p].maxn=val,void();
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) modify(p<<1,x,val);
else modify(p<<1|1,x,val);
pushup(p);
}
LL ask(int p,int l,int r){
if(l>r) return -inf;
if(l<=t[p].l&&t[p].r<=r) return t[p].maxn;
int mid=(t[p].l+t[p].r)>>1;
LL res=-inf;
if(l<=mid) res=max(res,ask(p<<1,l,r));
if(r>mid) res=max(res,ask(p<<1|1,l,r));
return res;
}
}Seg2;
struct SegmentTree3{
int tot,rt[N];
struct node{
int l,r,ls,rs;
LL addx,addy;
Node2 val;
void tagx(LL dx){ addx+=dx,val.x+=dx,val.xy+=dx; }
void tagy(LL dy){ addy+=dy,val.y+=dy,val.xy+=dy; }
}t[N<<2];
LL Val(int p){ return t[rt[p]].val.xy; }
int New(int l,int r){
++tot;
t[tot].l=l,t[tot].r=r,t[tot].ls=t[tot].rs=t[tot].addx=t[tot].addy=0;
t[tot].val={0,0,0};
return tot;
}
void pushup(int p){ t[p].val=t[ls(p)].val+t[rs(p)].val; }
void spread(int p){
if(t[p].addx) t[ls(p)].tagx(t[p].addx),t[rs(p)].tagx(t[p].addx),t[p].addx=0;
if(t[p].addy) t[ls(p)].tagy(t[p].addy),t[rs(p)].tagy(t[p].addy),t[p].addy=0;
}
int build(int l,int r){
int p=New(l,r);
if(l==r){
t[p].val.x=B[l],t[p].val.y=A[l],t[p].val.xy=-inf;
return p;
}
int mid=(l+r)>>1;
ls(p)=build(l,mid),rs(p)=build(mid+1,r);
return pushup(p),p;
}
void changex(int p,int l,int r,LL dx){
if(l<=t[p].l&&t[p].r<=r) return t[p].tagx(dx),void();
spread(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) changex(ls(p),l,r,dx);
if(r>mid) changex(rs(p),l,r,dx);
pushup(p);
}
void changey(int p,int l,int r,LL dy){
if(l<=t[p].l&&t[p].r<=r) return t[p].tagy(dy),void();
spread(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) changey(ls(p),l,r,dy);
if(r>mid) changey(rs(p),l,r,dy);
pushup(p);
}
LL queryx(int p,int l,int r){
if(l>r) return -inf;
if(l<=t[p].l&&t[p].r<=r) return t[p].val.x;
spread(p);
int mid=(t[p].l+t[p].r)>>1;
LL res=-inf;
if(l<=mid) res=max(res,queryx(ls(p),l,r));
if(r>mid) res=max(res,queryx(rs(p),l,r));
return res;
}
LL queryy(int p,int l,int r){
if(l>r) return -inf;
if(l<=t[p].l&&t[p].r<=r) return t[p].val.y;
spread(p);
int mid=(t[p].l+t[p].r)>>1;
LL res=-inf;
if(l<=mid) res=max(res,queryy(ls(p),l,r));
if(r>mid) res=max(res,queryy(rs(p),l,r));
return res;
}
Node2 ask(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].val;
spread(p);
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) return ask(ls(p),l,r);
else if(l>mid) return ask(rs(p),l,r);
else return ask(ls(p),l,r)+ask(rs(p),l,r);
}
}Seg3;
struct SegmentTree4{
struct node{
int l,r;
LL maxn;
}t[N<<2];
void pushup(int p){
t[p].maxn=max(t[p<<1].maxn,t[p<<1|1].maxn);
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r) return t[p].maxn=Seg3.Val(l),void();
int mid=(l+r)>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
pushup(p);
}
void modify(int p,int x,LL val){
if(t[p].l==t[p].r) return t[p].maxn=val,void();
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) modify(p<<1,x,val);
else modify(p<<1|1,x,val);
pushup(p);
}
LL ask(int p,int l,int r){
if(l>r) return -inf;
if(l<=t[p].l&&t[p].r<=r) return t[p].maxn;
int mid=(t[p].l+t[p].r)>>1;
LL res=-inf;
if(l<=mid) res=max(res,ask(p<<1,l,r));
if(r>mid) res=max(res,ask(p<<1|1,l,r));
return res;
}
}Seg4;
void Init(){
t=(n+c-1)/c;
for(int i=1;i<=t;i++){
L[i]=R[i-1]+1,R[i]=min(n,i*c);
for(int j=L[i];j<=R[i];j++) id[j]=i;
}
for(int i=1;i<=t;i++) Seg1.rt[i]=Seg1.build(L[i],R[i]);
Seg2.build(1,1,t);
for(int i=1,j=c+1;i<=n;i++,j++){
A[i]=s[R[id[i]]]-s[i-1];
if(j>n) B[i]=-inf;
else B[i]=s[j]-s[L[id[j]]-1];
}
for(int i=1;i<=t;i++) Seg3.rt[i]=Seg3.build(L[i],R[i]);
Seg4.build(1,1,t);
}
void Modify(int x,int y){
int p=id[x],d=y-a[x];
a[x]=y;
Seg1.modify(Seg1.rt[p],x,y);
Seg2.modify(1,p,Seg1.Val(p));
if(p>1) Seg3.changex(Seg3.rt[p-1],x-c,R[p]-c,d),Seg4.modify(1,p-1,Seg3.Val(p-1));
Seg3.changey(Seg3.rt[p],L[p],x,d),Seg4.modify(1,p,Seg3.Val(p));
}
LL solve(int p,int x,int y,int u,int v){
u-=c,v-=c;
p=Seg3.rt[p];
if(x>v) return Seg3.queryx(p,u,v)+Seg3.queryy(p,x,y);
return max({Seg3.queryx(p,u,x-1)+Seg3.queryy(p,x,y),Seg3.ask(p,x,v).xy,Seg3.queryx(p,x,v)+Seg3.queryy(p,v+1,y)});
}
LL Query(int x,int y){
int p=id[x],q=id[y];
LL ans=-inf;
if(p==q) return Seg1.ask(Seg1.rt[p],x,y).maxn;
ans=max({Seg1.ask(Seg1.rt[p],x,R[p]).maxn,Seg1.ask(Seg1.rt[q],L[q],y).maxn,Seg2.ask(1,p+1,q-1)});
ans=max(ans,Seg4.ask(1,p+1,q-2));
if(p+1==q) ans=max(ans,solve(p,x,R[p],L[q],y));
else ans=max({ans,solve(p,x,R[p],L[p+1],R[p+1]),solve(q-1,L[q-1],R[q-1],L[q],y)});
return ans;
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
double beg=clock();
scanf("%d%d%d",&n,&T,&c);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
Init();
while(T--){
int op,x,y; scanf("%d%d%d",&op,&x,&y);
if(op==1) Modify(x,y);
else printf("%lld\n",Query(x,y));
}
cerr << "Time: " << (clock()-beg) << endl;
return 0;
}
II.[NOIP2025] 序列询问
题意:\(q\) 次询问,每次询问给出 \(L,R\),对每个 \(i\) 求出包含它且区间长度在 \([L,R]\) 中的最大子段和。单次询问可以 \(O(n)\)。
设 \(ans_i\) 表示 \(i\) 的答案,\(s_i\) 表示原序列的前缀和。
先把子段和拆成前缀和相减的形式,然后考虑一个比较经典的 \(O(nq\log n)\) 做法,对序列分治,只考虑跨过 \(mid\) 的区间 \([l,r]\),然后以左半区间的点 \(i\) 为例,只需要满足 \(l\le i\) 就能让 \(i\) 被 \([l,r]\) 包含,那么我们对每个 \(l\) 用滑动窗口求出合法的最优的 \(r\),把此时的答案记为 \(val_l\),然后对 \(val\) 做一个前缀 \(\max\),再贡献到 \(ans_l\) 即可。
这个做法写的漂亮一点再卡卡常就能获得 \(100pts\) 的部分分
然后正解考虑我们的定长分块 trick,先把原序列以 \(R\) 为块长分块,那么合法区间不可能跨过一整块。对于跨过两个相邻整块分界点的情况,这个分界点就为我们提供了天然的分治中心,套用上面分治的做法即可。
对于整块内的情况,此时已经不用考虑区间长度上界 \(R\) 的问题了,于是我们继续定长分块,把整个块再以 \(L\) 为块长分块,对每个小块 \([x,y]\) 单独考虑,如果区间在小块内显然不合法,否则:
- 如果最后的区间包含这个小块:一定合法,问题变成求 \(\max_{l\le x,r\ge y}(s_r-s_{l-1})\),因为 \(l,r\) 独立,所以预处理 \(s\) 的前缀 \(\min\) 和后缀 \(\max\) 即可。
- 如果最后的区间 \([l,r]\) 只有一部分在这个小块内,以 \(l\in [x,y],r\ge \max(y+1,l+L-1)\) 为例:通过预处理的后缀 \(\max\) 可以 \(O(1)\) 得出每个 \(l\) 的答案,然后再把每个 \(l\) 的答案做前缀 \(\max\) 贡献到 \(ans\) 即可。(类似分治部分左半区间的处理方法,只不过由于只有 \(len \ge L\) 一个限制所以不需要滑动窗口)
复杂度 \(O(qn)\),如果你不喜欢滑动窗口可以用 ST 表代替。
code
洛谷上可以跑进 \(2s\),但是 QOJ 上貌似被 hack 了,不过我懒得卡常了。
点击查看代码
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define LL long long
#define ULL unsigned long long
using namespace std;
const int N=5e4+5;
const LL inf=1e10;
int n,a[N],T,L,R;
LL s[N],ans[N],val[N],pre[N],suf[N];
void chkmax(LL &x,LL y){ if(y>x) x=y; }
struct Deque{
int dq[N],l,r;
void init(){ l=1,r=0; }
bool empty(){ return l>r; }
int front(){ return dq[l]; }
int back(){ return dq[r]; }
void pop_back(){ r--; }
void pop_front(){ l++; }
void push_back(int x){ dq[++r]=x; }
}dq;
void solve(int l,int r){
pre[l-1]=inf,suf[r+1]=-inf;
for(int i=l;i<=r;i++) pre[i]=min(pre[i-1],s[i-1]);
for(int i=r;i>=l;i--) suf[i]=max(suf[i+1],s[i]);
for(int x=l;x<=r;x+=L){
int y=min(x+L-1,r);
val[x-1]=-inf;
for(int i=x;i<=y;i++){
if(i+L-1<=r) val[i]=suf[i+L-1]-s[i-1];
else val[i]=-inf;
chkmax(val[i],val[i-1]);
chkmax(ans[i],val[i]);
}
val[y+1]=-inf;
for(int i=y;i>=x;i--){
if(i-L+1>=l) val[i]=s[i]-pre[i-L+1];
else val[i]=-inf;
chkmax(val[i],val[i+1]);
chkmax(ans[i],val[i]);
}
if(x!=l&&y!=r){
LL V=suf[y]-pre[x];
for(int i=x;i<=y;i++) chkmax(ans[i],V);
}
}
}
void Print(){
ULL res=0;
for(int i=1;i<=n;i++) res^=(ULL)i*ans[i];
printf("%llu\n",res);
}
signed main(){
// freopen("query.in","r",stdin);
// freopen("query.out","w",stdout);
double beg=clock();
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
scanf("%d",&T);
while(T--){
scanf("%d%d",&L,&R);
for(int i=1;i<=n;i++) ans[i]=-inf;
for(int i=1;i<=n;i+=R) solve(i,min(n,i+R-1));
for(int l1=1;l1+R<=n;l1+=R){
int r1=l1+R-1,l2=r1+1,r2=min(n,l2+R-1);
dq.init();
val[l1]=-inf;
for(int i=l1+1,j=l2;i<=r1;i++){
while(j<=r2&&j-i+1<=R){
while(!dq.empty()&&s[j]>s[dq.back()]) dq.pop_back();
dq.push_back(j);
j++;
}
while(!dq.empty()&&dq.front()-i+1<L) dq.pop_front();
if(dq.empty()) val[i]=-inf;
else val[i]=s[dq.front()]-s[i-1];
chkmax(val[i],val[i-1]);
chkmax(ans[i],val[i]);
}
dq.init();
val[r2+1]=-inf;
for(int i=r2,j=r1;i>=l2;i--){
while(j>=l1&&i-j+1<=R){
while(!dq.empty()&&s[j-1]<s[dq.back()-1]) dq.pop_back();
dq.push_back(j);
j--;
}
while(!dq.empty()&&i-dq.front()+1<L) dq.pop_front();
if(dq.empty()) val[i]=-inf;
else val[i]=s[i]-s[dq.front()-1];
chkmax(val[i],val[i+1]);
chkmax(ans[i],val[i]);
}
}
Print();
}
cerr << "Time: " << (clock()-beg) << endl;
return 0;
}

浙公网安备 33010602011771号