定长分块

被 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;
}
posted @ 2026-01-08 15:17  Green&White  阅读(5)  评论(0)    收藏  举报