分块

分块

定义:一种暴力数据结构,即将数列分为 \(w\) 块,以块为单位维护一些信息。

P3372

如何使用分块实现线段树?

考虑区间修改:散块暴力修改,整块打标记(维护 \(tag_i\))即可。

考虑区间查询:散块暴力求和,整块整体求和(维护 \(sum_i\))即可。

这是个很简单的题目,却透露出一个通用的分块思路:散块暴力、整块维护

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e5+5;
int n,m;
int a[N],sum[N],tag[N],L[N],R[N],pos[N]; 

void upd(int l,int r,int k){
	int x=pos[l],y=pos[r];
	if(x==y){
		for(int i=l;i<=r;i++)
			a[i]+=k,sum[x]+=k;
	}
	else{
		for(int i=l;i<=R[x];i++)
			a[i]+=k,sum[x]+=k;
		for(int i=x+1;i<y;i++)
			tag[i]+=k;
		for(int i=L[y];i<=r;i++)
			a[i]+=k,sum[y]+=k;
	}
}
int qry(int l,int r){
	int x=pos[l],y=pos[r],res=0;
	if(x==y){
		for(int i=l;i<=r;i++)
			res+=a[i]+tag[x];
	}
	else{
		for(int i=l;i<=R[x];i++)
			res+=a[i]+tag[x];
		for(int i=x+1;i<y;i++)
			res+=sum[i]+(R[i]-L[i]+1)*tag[i];
		for(int i=L[y];i<=r;i++)
			res+=a[i]+tag[y];
	}
	return res;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int t=sqrt(n);
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*sqrt(n)+1;
		R[i]=i*sqrt(n);
	}
	if(R[t]<n){
		t++;
		L[t]=R[t-1]+1;
		R[t]=n;
	}
	for(int i=1;i<=t;i++)
		for(int j=L[i];j<=R[i];j++)
			sum[i]+=a[j],pos[j]=i;
	while(m--){
		int op,x,y,k;
		cin>>op>>x>>y;
		if(op==1){
			cin>>k;
			upd(x,y,k);
		}
		else
			cout<<qry(x,y)<<'\n';
	}
	return 0;
}

P2801 & P5356

对于这道题,显然我们容易想到对于每个块维护一个有序数组,这样就可以用二分的方式回答询问了。

考虑区间修改:同上,注意散块修改后需要重新排序。

考虑区间查询:散块暴力是简单的,整块查询因为是整体加 \(tag_i\),有序性不变,仅需进行二分即可。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e6+5;
int n,q;
int a[N],sum[N],tag[N],L[N],R[N],pos[N];
vector<int> g[N];

void resort(int x){
	g[x].clear();
	for(int i=L[x];i<=R[x];i++)
		g[x].push_back(a[i]);
	sort(g[x].begin(),g[x].end());
}
int fnd(int a,int x){
	int l=-1,r=g[a].size();
	while(l+1<r){
		int mid=(l+r)>>1;
		if(g[a][mid]>=x)
			r=mid;
		else
			l=mid;
	}
	return g[a].size()-r;
}
void upd(int l,int r,int k){
	int x=pos[l],y=pos[r];
	if(x==y){
		for(int i=l;i<=r;i++)
			a[i]+=k;
		resort(x);
	}
	else{
		for(int i=l;i<=R[x];i++)
			a[i]+=k;
		resort(x);
		for(int i=x+1;i<y;i++)
			tag[i]+=k;
		for(int i=L[y];i<=r;i++)
			a[i]+=k;
		resort(y);
	}
}
int qry(int l,int r,int c){
	int x=pos[l],y=pos[r],res=0;
	if(x==y){
		for(int i=l;i<=r;i++)
			res+=(a[i]+tag[x]>=c);
	}
	else{
		for(int i=l;i<=R[x];i++)
			res+=(a[i]+tag[x]>=c);
		for(int i=x+1;i<y;i++)
			res+=fnd(i,c-tag[i]);
		for(int i=L[y];i<=r;i++)
			res+=(a[i]+tag[y]>=c);
	}
	return res;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int t=sqrt(n);
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*sqrt(n)+1;
		R[i]=i*sqrt(n);
	}
	if(R[t]<n){
		t++;
		L[t]=R[t-1]+1;
		R[t]=n;
	}
	for(int i=1;i<=t;i++){
		for(int j=L[i];j<=R[i];j++)
			g[i].push_back(a[j]),pos[j]=i;
		sort(g[i].begin(),g[i].end());
	}
	while(q--){
		char op;
		int x,y,k;
		cin>>op>>x>>y>>k;
		if(op=='M')
			upd(x,y,k);
		else
			cout<<qry(x,y,k)<<'\n';
	}
	return 0;
}

至于 P5356,需要外边还套一个值域二分,思路很简单但是代码很大且需要卡常,反正我是卡不动了,30分记录

总结:查排名对应的数 / 数对应的排名,考虑排序

P4145

诈骗题,看上去十分唬人,实际上不难发现开方开了 \(5\) 次以上就会都变成 \(1\),这以后就无需修改了,于是暴力修改即可,代码很好写。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e5+5;
int n,m;
int a[N],pos[N],L[N],R[N],sum[N],cs[N];

void upd(int l,int r){
	int x=pos[l],y=pos[r];
	if(x==y){
		if(cs[x]<=5)
			for(int i=l;i<=r;i++)
				sum[x]-=a[i],a[i]=sqrt(a[i]),sum[x]+=a[i];
	}
	else{
		if(cs[x]<=5)
			for(int i=l;i<=R[x];i++)
				sum[x]-=a[i],a[i]=sqrt(a[i]),sum[x]+=a[i];
		for(int i=x+1;i<y;i++){
			if(cs[i]<=5){
				for(int j=L[i];j<=R[i];j++){
					sum[i]-=a[j];
					a[j]=sqrt(a[j]);
					sum[i]+=a[j];
				}
				cs[i]++;
			}
		}
		if(cs[y]<=5)
			for(int i=L[y];i<=r;i++)
				sum[y]-=a[i],a[i]=sqrt(a[i]),sum[y]+=a[i];
	}
}
int qry(int l,int r){
	int x=pos[l],y=pos[r],res=0;
	if(x==y)
		for(int i=l;i<=r;i++)
			res+=a[i];
	else{
		for(int i=l;i<=R[x];i++)
			res+=a[i];
		for(int i=x+1;i<y;i++)
			res+=sum[i];
		for(int i=L[y];i<=r;i++)
			res+=a[i];
	}
	return res;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int t=sqrt(n);
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*sqrt(n)+1;
		R[i]=i*sqrt(n);
	}
	if(R[t]<n){
		t++;
		L[t]=R[t-1]+1;
		R[t]=n;
	}
	for(int i=1;i<=t;i++)
		for(int j=L[i];j<=R[i];j++)
			pos[j]=i,sum[i]+=a[j];
	cin>>m;
	while(m--){
		int k,l,r;
		cin>>k>>l>>r;
		if(l>r)
			swap(l,r);
		if(!k)
			upd(l,r);
		else
			cout<<qry(l,r)<<'\n';
	}
	return 0;
} 

总结:先发掘性质再做题。

P3203

先考虑暴力,如果没有修改操作,直接倒序做一遍 dp 即可回答询问;这里有修改操作,于是每次修改后必须重新做一遍 dp,太 man 了,考虑用一个分块优化之。

以块为单位考虑,以前我们维护的是每个点的后继是哪里和跳出界要多少步,现在我们就应当维护每个点跳出块以后到了哪里和跳出块要多少步。

对于询问,一个一个块地跳即可;对于修改,块内暴力修改即可。

这是一个极具启发性的好题,它表明分块的意义即在于缩小考虑范围(从全局缩小到每个块),处理好每个块的信息再进行合并。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+5;
int n,m;
int a[N],pos[N],L[N],R[N],step[N],to[N];

void upd(int x,int k){
	a[x]=k;
	for(int i=R[pos[x]];i>=L[pos[x]];i--){
		to[i]=i+a[i];
		if(to[i]>R[pos[x]])
			step[i]=1;
		else
			step[i]=step[to[i]]+1,to[i]=to[to[i]];
	}
}
int qry(int x){
	int res=0;
	while(x<=n)
		res+=step[x],x=to[x];
	return res;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int t=sqrt(n);
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*sqrt(n)+1;
		R[i]=i*sqrt(n);
	}
	if(R[t]<n){
		t++;
		L[t]=R[t-1]+1;
		R[t]=n;
	}
	for(int i=1;i<=t;i++)
		for(int j=L[i];j<=R[i];j++)
			pos[j]=i;
	for(int i=n;i>=1;i--){
		to[i]=i+a[i];
		if(to[i]>R[pos[i]])
			step[i]=1;
		else
			step[i]=step[to[i]]+1,to[i]=to[to[i]];
	}
	cin>>m;
	while(m--){
		int op,x,k;
		cin>>op>>x;
		x++;
		if(op==2)
			cin>>k,upd(x,k);
		else
			cout<<qry(x)<<'\n';
	}
	return 0;
} 

P1975

这个题,我们考虑处理增量。

考虑 \(x,y\) 交换以后,会产生三部分增量:

  • \([x+1,y-1]\) 中,原来比 \(x\) 大的和比 \(y\) 小的,会贡献逆序对;

  • 反之,比 \(x\) 小的和比 \(y\) 大的,会扣除逆序对;

  • 最后,若 \(x<y\) 则增加一个逆序对,\(x>y\) 扣除一个。

然后我们发现这转化为了一个求比某个数大 / 小的元素有多少个,可以使用分块解决(类似“教主的魔法”)。

code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#include <cassert>
#define int long long
using namespace std;

const int N=1e6+5;
int n,m,a[N];
int tree[N];
struct H{
	int val,id;
}h[N];
int L[N],R[N],pos[N];
vector<int> g[N];

int lowbit(int x){
	return x&(-x);
}
void upd(int x,int y){
	for(;x<=n;x+=lowbit(x)) tree[x]+=y;
}
int qry(int x){
	int s=0;
	for(;x;x-=lowbit(x)) s+=tree[x];
	return s;
}
bool cmp(H x,H y){
	if(x.val==y.val)
		return x.id>y.id;
	return x.val>y.val;
}
void resort(int x){
	g[x].clear();
	for(int i=L[x];i<=R[x];i++)
		g[x].push_back(a[i]);
	sort(g[x].begin(),g[x].end());
}
int fndup(int a,int x){
	int l=-1,r=g[a].size();
	while(l+1<r){
		int mid=(l+r)>>1;
		if(g[a][mid]>x)
			r=mid;
		else
			l=mid;
	}
	return g[a].size()-r;
}
int fnddown(int a,int x){
	int l=-1,r=g[a].size();
	while(l+1<r){
		int mid=(l+r)>>1;
		if(g[a][mid]<x)
			l=mid;
		else
			r=mid;
	}
	return l+1;
}
int qryup(int l,int r,int c){
	if(l>r)
		return 0;
	int x=pos[l],y=pos[r],res=0;
	if(x==y){
		for(int i=l;i<=r;i++)
			res+=(a[i]>c);
	}
	else{
		for(int i=l;i<=R[x];i++)
			res+=(a[i]>c);
		for(int i=x+1;i<y;i++)
			res+=fndup(i,c);
		for(int i=L[y];i<=r;i++)
			res+=(a[i]>c);
	}
	return res;
}
int qrydown(int l,int r,int c){
	if(l>r)
		return 0;
	//assert(l >= 1 && l <= n);
    //assert(r >= 1 && r <= n);
    //assert(l <= r);
	int x=pos[l],y=pos[r],res=0;
	if(x==y){
		for(int i=l;i<=r;i++)
			res+=(a[i]<c);
	}
	else{
		for(int i=l;i<=R[x];i++)
			res+=(a[i]<c);
		for(int i=x+1;i<y;i++)
			res+=fnddown(i,c);
		for(int i=L[y];i<=r;i++)
			res+=(a[i]<c);
	}
	return res;
}

signed main(){
	//freopen("1975.txt","r",stdin);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	//assert(n >= 1 && n <= 20000);
	for(int i=1;i<=n;i++)
		cin>>a[i],h[i].val=a[i],h[i].id=i;
	sort(h+1,h+n+1,cmp);
	int ans=0;
	for(int i=1;i<=n;i++){
		ans+=qry(h[i].id-1);
		upd(h[i].id,1);
	}
	cout<<ans<<'\n';
	int t=sqrt(n);
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*sqrt(n)+1;
		R[i]=i*sqrt(n);
		//assert(L[i] <= R[i]);
	}
	if(R[t]<n){
		t++;
		L[t]=R[t-1]+1;
		R[t]=n;
		//assert(L[t] <= R[t]);
	}
	for(int i=1;i<=t;i++){
		for(int j=L[i];j<=R[i];j++)
			g[i].push_back(a[j]),pos[j]=i;
		sort(g[i].begin(),g[i].end());
	}
	cin>>m;
	//assert(m >= 1 && m <= 2000);
	while(m--){
		int x,y;
		cin>>x>>y;
		if(x>y)
			swap(x,y);
		//assert(x >= 1 && x <= n);
    	//assert(y >= 1 && y <= n);
    	//assert(x != y);
		int xx=a[x],yy=a[y];
		swap(a[x],a[y]);
		resort(pos[x]),resort(pos[y]);
		//assert(is_sorted(g[pos[x]].begin(), g[pos[x]].end()));
		//assert(is_sorted(g[pos[y]].begin(), g[pos[y]].end()));
		ans-=qrydown(x+1,y-1,xx);
		ans+=qryup(x+1,y-1,xx);
		ans-=qryup(x+1,y-1,yy);
		ans+=qrydown(x+1,y-1,yy);
		if(yy>xx)
			ans++;
		else if(yy<xx)
			ans--;
		//assert(ans >= 0);
		cout<<ans<<'\n';
	}
	return 0;
}

总结:

  • 处理增量思想。

  • 显式处理非法情况、调试用 assert(血的教训)。

P3710

逆天分块。

若没有撤销操作,直接线段树维护即可。

考虑到有这个撤销操作,一种暴力的方式就是重新做一遍,显然这太 man 了。

我们考虑缩小这个暴力的范围,即对于操作序列进行分块,每次对于被撤销的操作的块重新做一遍,这样撤销的时间复杂度可以降至单次 \(O(\sqrt{m} \log n)\)

同时,每一个块需要一棵线段树,维护 \(a,b\) 两个值,表示 \(x\) 经过这个块之后能变为 \(ax+b\)。于是对于查询操作,直接从前缀的块累加过来即可。\(O(\sqrt{m} \log n)\)

时间复杂度降下来了,但是空间复杂度飙升至 \(O(n \sqrt{m})\),炸了。

两个方法:

  • \(l,r\) 离散化。

  • 调块长。

一些细节:

  • 撤销后的操作使用并查集跳过。

  • 取模操作尽量用减法代替。

  • 线段树开大空间。

然后理论上就可以过了。

code(233 行,4KB)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define sil static inline
using namespace std;

inline int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; }
void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; }


const int N=150001,M=1e3+5;
const long long MOD=998244353;
int n,m,all,siz,t;
int L[M],R[M],pos[N];
int g[M][M*4+5],tot[M];
int fa[N],root[N];
struct Q{
	int op,l,r,d,p;
}q[N];
struct SGT{
	int lt,rt;
	long long a,b,addtag,multag;
	bool flag;
}tr[N*20];

sil int ask(int x,int a){
	return lower_bound(g[a]+1,g[a]+tot[a]+1,x)-g[a];
}
sil int fnd(int x){
	return (fa[x]==x?x:fa[x]=fnd(fa[x]));
}
sil void make(int id,int addtag,int multag,int flag){
	if(flag){
		tr[id].a=tr[id].multag=1;
		tr[id].b=tr[id].addtag=0;
		tr[id].flag=1;
	}
	if(multag!=1){
		tr[id].a=(tr[id].a*multag%MOD)%MOD;
		tr[id].multag=(tr[id].multag*multag%MOD)%MOD;
		tr[id].b=(tr[id].b*multag%MOD)%MOD;
		tr[id].addtag=(tr[id].addtag*multag%MOD)%MOD;
	}
	if(addtag!=0){
		tr[id].b=tr[id].b+addtag;
		tr[id].b=(tr[id].b>MOD?tr[id].b-MOD:tr[id].b);
		tr[id].addtag=tr[id].addtag+addtag;
		tr[id].addtag=(tr[id].addtag>MOD?tr[id].addtag-MOD:tr[id].addtag);
	}
}
sil int build(int lt,int rt){
	int id=++all;
	tr[id].a=tr[id].multag=1;
	if(lt==rt)
		return id;
	int mid=(lt+rt)>>1;
	tr[id].lt=build(lt,mid);
	tr[id].rt=build(mid+1,rt);
	return id;
}
sil void upd_add(int p,int ql,int qr,int val,int lt,int rt){
	if(ql<=lt&&rt<=qr){
		make(p,val,1,0);
		return;
	}
	make(tr[p].lt,tr[p].addtag,tr[p].multag,tr[p].flag);
	make(tr[p].rt,tr[p].addtag,tr[p].multag,tr[p].flag);
	tr[p].addtag=0,tr[p].multag=1,tr[p].flag=0;
	int mid=(lt+rt)>>1;
	if(ql<=mid)
		upd_add(tr[p].lt,ql,qr,val,lt,mid);
	if(qr>mid)
		upd_add(tr[p].rt,ql,qr,val,mid+1,rt);
}
sil void upd_mul(int p,int ql,int qr,int val,int lt,int rt){
	if(ql<=lt&&rt<=qr){
		make(p,0,val,0);
		return;
	}
	make(tr[p].lt,tr[p].addtag,tr[p].multag,tr[p].flag);
	make(tr[p].rt,tr[p].addtag,tr[p].multag,tr[p].flag);
	tr[p].addtag=0,tr[p].multag=1,tr[p].flag=0;
	int mid=(lt+rt)>>1;
	if(ql<=mid)
		upd_mul(tr[p].lt,ql,qr,val,lt,mid);
	if(qr>mid)
		upd_mul(tr[p].rt,ql,qr,val,mid+1,rt);
}
sil void qry(int p,int pos,int lt,int rt,long long& val){
	if(lt==rt){
		val=tr[p].a%MOD*1ll*val+tr[p].b;
		val=(val>MOD?val-MOD:val);
		return;
	}
	int mid=(lt+rt)>>1;
	make(tr[p].lt,tr[p].addtag,tr[p].multag,tr[p].flag);
	make(tr[p].rt,tr[p].addtag,tr[p].multag,tr[p].flag);
	tr[p].addtag=0,tr[p].multag=1,tr[p].flag=0;
	if(pos<=mid){
		qry(tr[p].lt,pos,lt,mid,val);
		return;
	}
	else{
		qry(tr[p].rt,pos,mid+1,rt,val);
		return;
	}
}
sil void blk_init(){
	t=max((int)sqrt(1.50066*m),77);
	siz=m/t;
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*siz+1;
		R[i]=i*siz;
	}
	if(R[t]<m){
		t++;
		L[t]=R[t-1]+1;
		R[t]=m;
	}
	for(int i=1;i<=t;i++)
		for(int j=L[i];j<=R[i];j++)
			pos[j]=i;
}
sil void dsc_init(){
	for(int i=1;i<=m+1;i++)
		fa[i]=i;
	for(int i=1;i<=t;i++){
		g[i][++tot[i]]=n;
		for(int j=L[i];j<=R[i];j++){
			if(q[j].op<3){
				g[i][++tot[i]]=q[j].l;
				g[i][++tot[i]]=q[j].r;
				g[i][++tot[i]]=q[j].l-1;
				g[i][++tot[i]]=q[j].r-1;
			}
		}
		sort(g[i]+1,g[i]+tot[i]+1);
		tot[i]=unique(g[i]+1,g[i]+tot[i]+1)-g[i]-1;
		for(int j=L[i];j<=R[i];j++){
			if(q[j].op<3){
				q[j].l=ask(q[j].l,i);
				q[j].r=ask(q[j].r,i);
			}
			else
				fa[j]=fnd(j+1);
		}
		root[i]=build(1,tot[i]);
	}
}

signed main(){
	//freopen("P3710_1.txt","r",stdin);
	//freopen("gogogo.txt","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		q[i].op=read();
		if(q[i].op<=2){
			q[i].l=read(),q[i].r=read(),q[i].d=read();
			q[i].d=(q[i].d>MOD?q[i].d-MOD:q[i].d);
		}
		if(q[i].op>=3)
			q[i].p=read();
	}
	blk_init();
	dsc_init();
	for(int i=1;i<=m;i++){
		if(q[i].op==1)
			upd_add(root[pos[i]],q[i].l,q[i].r,q[i].d,1,tot[pos[i]]);
		else if(q[i].op==2)
			upd_mul(root[pos[i]],q[i].l,q[i].r,q[i].d,1,tot[pos[i]]);
		else if(q[i].op==3){
			long long ans=0;
			for(int j=1;j<pos[i];j++)
				qry(root[j],ask(q[i].p,j),1,tot[j],ans),ans%=MOD;
			for(int j=fnd(L[pos[i]]);j<=i;j=fnd(j+1)){
				int cur=ask(q[i].p,pos[i]);
				if(q[j].op==1&&q[j].l<=cur&&cur<=q[j].r)
					ans+=q[j].d,ans=(ans>MOD?ans-MOD:ans);
				if(q[j].op==2&&q[j].l<=cur&&cur<=q[j].r)
					ans*=q[j].d,ans%=MOD;
			}
			write(ans%MOD),putchar('\n');
		}
		else{
			fa[q[i].p]=fnd(q[i].p+1);
			make(root[pos[q[i].p]],0,1,1);
			int lt=fnd(L[pos[q[i].p]]),rt=min(R[pos[q[i].p]],i);
			for(int j=lt;j<=rt;j=fnd(j+1)){
				if(q[j].op==1)
					upd_add(root[pos[q[i].p]],q[j].l,q[j].r,q[j].d,1,tot[pos[q[i].p]]);
				if(q[j].op==2)
					upd_mul(root[pos[q[i].p]],q[j].l,q[j].r,q[j].d,1,tot[pos[q[i].p]]);
			}
		}
	}
	return 0;
}
/*
500 24
1 150 496 322370810
4 1
1 188 219 961522103
1 131 493 209961579
2 85 408 591218226
2 317 446 129855108
3 306
1 26 360 576330848
1 211 307 1063082822
2 181 344 117319866
3 388
3 461
3 306
2 317 443 649178399
2 223 346 46701898
1 14 276 859673089
1 18 308 849166726
2 29 315 1037051746
3 436
1 70 89 528712138
1 34 355 859799420
3 39
2 11 34 786295242
3 367
*/

下辈子再也不会做这种题了吗的。

总结:

  • 分块题先想暴力,再缩小范围。

  • 对于操作序列分块的思想很重要。

posted @ 2025-03-15 12:48  _KidA  阅读(17)  评论(0)    收藏  举报