操作分块

操作分块:有若干修改,询问组成的操作序列,考虑把这个操作序列分块,然后维护块内和块间的贡献,并根据实际情况平衡两种维护方式的复杂度。
当遇到大量操作并且难以用普通数据结构维护的时候,可以考虑操作分块,并进行如下思考:
\(1\):当仅有\(\sqrt n\)次操作时,如何在\(O(\sqrt n^2)\)内维护。
\(2\):如何在单块\(O(\sqrt n)内维护前面\)O(n)$长度对于当前块的贡献。
\(3\):是否存在根号分治可以平衡的暴力做法。
要注意往往操作分块并不是正解,并且其复杂度较劣,运用时慎重考虑。
通过几道例题深入了解:
\(P5443\)
https://www.luogu.com.cn/problem/P5443
题解:先考虑暴力做法。
暴力\(1\):对每个修改直接暴力修改,对每个询问,将所有权值大于等于\(w\)的边加入并查集,最后回答\(s\)所在集合大小即可。
修改\(O(1)\),查询\(O(m)\)
暴力\(2\):考虑对所有边,所有的询问从大到小排序。
对于边,分为带修边和不带修边,对于不带修边直接随着询问动态加入并查集即可。
而对于有修改的边,每次询问时,暴力枚举每条边对应的修改操作,选择时间小于等于当前询问时间里,时间最大的一条边。若这条边重量大于等于询问重量,则将这条边加入到可撤销并查集中,每次询问完,撤销本次加入的带修边。
发现对于每次询问,暴力\(1\)枚举了所有边,而暴力\(2\)枚举了所有操作,考虑平衡。
\(S\)个操作放在一个块里,对于处理一个块,先将所有边和块内询问按重量排序,然后根据是否存在块内的修改操作分为带修边和不带修边。对于带修边和不带修边的处理,都和暴力\(2\)中相同。处理完一个块后,对块内的修改统一直接修改即可。
注意根据操作复杂度平衡块长,本题取\(\sqrt{mlogn}\)较为优秀。

#include <bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;
const int N=1e5+10;

int n,m,k;
int ans[N];
vector<PII> q[N];

struct Edge{
	int x,y,z;
	bool operator <(Edge t){
		return z<t.z;
	}
}e[N];

struct Operator{
	int tp,x,y;
}op[N];

struct Change{
	int tm,x,y;
	bool operator <(Change t){
		return y<t.y;
	}
};

struct DisjointSetUnion{
	int p[N],sz[N];
	int top;
	PII stk[N];
	void init(){
		top=0;
		for(int i=1; i<=n; i++) p[i]=i;
		for(int i=1; i<=n; i++) sz[i]=1;
	}
	int find(int x){
		if(x==p[x]) return x;
		return find(p[x]);
	}
	void merge(int x,int y){
		x=find(x),y=find(y);
		if(x==y) return;
		if(sz[x]>sz[y]) swap(x,y);
		p[x]=y,sz[y]+=sz[x];
		stk[++top]={x,y};
	}
	void del(){
		PII t=stk[top--];
		int x=t.first,y=t.second;
		sz[y]-=sz[x],p[x]=x;
	}
}dsu;

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for(int i=1; i<=m; i++){
		int x,y,z;
		cin >> x >> y >> z;
		e[i]={x,y,z};
	}
	cin >> k;
	for(int i=1; i<=k; i++){
		int tp,x,y;
		cin >> tp >> x >> y;
		op[i]={tp,x,y};
	}
	int len=sqrt(k*log2(n));
	for(int L=1; L<=k; L+=len){
		int R=min(k,L+len-1);
		for(int i=1; i<=m; i++) q[i].clear();
		vector<Change> task;
		vector<Edge> e1;
		vector<int> e2;
		for(int i=L; i<=R; i++)
			if(op[i].tp==1) q[op[i].x].push_back({i,op[i].y});
			else task.push_back({i,op[i].x,op[i].y});	
		for(int i=1; i<=m; i++)
			if(!q[i].size()) e1.push_back({e[i].x,e[i].y,e[i].z});
			else e2.push_back(i);
		sort(e1.begin(),e1.end());
		reverse(e1.begin(),e1.end());
		sort(task.begin(),task.end());
		reverse(task.begin(),task.end());
		dsu.init();
		for(int i=0,j=0; i<task.size(); i++){
			while(j<e1.size()&&e1[j].z>=task[i].y) 
				dsu.merge(e1[j].x,e1[j].y),j++;
			int tmp=dsu.top;
			for(int x=0; x<e2.size(); x++){
				int id=e2[x],l=0,r=q[id].size()-1;
				while(l<r){
					int mid=(l+r+1)/2;
					if(q[id][mid].first<=task[i].tm) l=mid;
					else r=mid-1;
				}	
				if(q[id][l].first>task[i].tm&&e[id].z>=task[i].y)
					dsu.merge(e[id].x,e[id].y);
				if(q[id][l].first<=task[i].tm&&q[id][l].second>=task[i].y)
					dsu.merge(e[id].x,e[id].y);				
			}
			ans[task[i].tm]=dsu.sz[dsu.find(task[i].x)];
			while(dsu.top>tmp) dsu.del();
		}
		for(int i=L; i<=R; i++)
			if(op[i].tp==1) e[op[i].x].z=op[i].y; 
	}
	for(int i=1; i<=k; i++)
		if(op[i].tp==2) cout << ans[i] << endl;
	return 0;
	
}

\(P3247\)
https://www.luogu.com.cn/problem/P3247
题解:对于每个询问,将所有满足\(x\leq a,y\leq b\),的边\((u,v,x,y)\)加入到并查集中,若\(u,v\)处于一个联通块,且该联通块两维度的最大值分别为\(a,b\),则询问正确。
考虑优化,将边按第一维度从小到大排序,并将询问放入块中。
考虑先前块对当前块的影响,只需要将先前块内边,和当前块内询问都按第二维度排序,针对第二维度双指针即可。
考虑当前块对当前块的影响,直接暴力判断边是否满足条件并加入即可,再撤销。

#include <bits/stdc++.h>

using namespace std;

const int N=2e5+10;

int n,m,k;
bool ans[N];

struct Edge{
	int x,y;
	int a,b;
}e[N];

bool cmp1(Edge t1,Edge t2){
	return t1.a<t2.a;
}

bool cmp2(Edge t1,Edge t2){
	return t1.b<t2.b;
}

struct Query{
	int id;
	int x,y;
	int a,b;
	bool operator <(Query t){
		return b<t.b;
	}
};
vector<Query> q[N];

struct DisjointSetUnion{
	int p[N],sz[N];
	int mx_a[N],mx_b[N];
	int top;
	Edge stk[N];
	void init(){
		top=0;
		for(int i=1; i<=n; i++) p[i]=i;
		for(int i=1; i<=n; i++) sz[i]=1;
		memset(mx_a,-1,sizeof mx_a);
		memset(mx_b,-1,sizeof mx_b);
	}
	int find(int x){
		if(x==p[x]) return x;
		return find(p[x]);
	}
	void merge(int x,int y,int a,int b){
		x=find(x),y=find(y);
		if(x==y){
			stk[++top]={x,y,mx_a[x],mx_b[x]};
			mx_a[x]=max(mx_a[x],a);
			mx_b[x]=max(mx_b[x],b);
			return;
		}
		if(sz[x]>sz[y]) swap(x,y);
		p[x]=y,sz[y]+=sz[x];
		stk[++top]={x,y,mx_a[y],mx_b[y]};
		mx_a[y]=max(mx_a[y],max(mx_a[x],a));
		mx_b[y]=max(mx_b[y],max(mx_b[x],b));
	}
	void del(){
		Edge t=stk[top--];
		int x=t.x,y=t.y,t_a=t.a,t_b=t.b;
		mx_a[y]=t_a,mx_b[y]=t_b;
		if(x!=y) sz[y]-=sz[x],p[x]=x;
	}
}dsu;

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for(int i=1; i<=m; i++){
		int x,y,a,b;
		cin >> x >> y >> a >> b;
		e[i]={x,y,a,b};
	}
	sort(e+1,e+m+1,cmp1);
	int len=sqrt(m),cnt=(m+len-1)/len+1;
	cin >> k;
	for(int i=1; i<=k; i++){
		int u,v,a,b;
		cin >> u >> v >> a >> b;
		int l=0,r=cnt-1;
		while(l<r){
			int mid=(l+r+1)/2;
			if(e[min(mid*len,m)].a<=a) l=mid;
			else r=mid-1;
		}
		q[l+1].push_back({i,u,v,a,b});
	}
	for(int i=1; i<=cnt; i++){
		int R=min((i-1)*len,m);
		sort(e+1,e+R+1,cmp2);
		sort(q[i].begin(),q[i].end());
		dsu.init();
		for(int j=0,now=1; j<q[i].size(); j++){
			Query t=q[i][j];
			while(now<=R&&e[now].b<=t.b) 
				dsu.merge(e[now].x,e[now].y,e[now].a,e[now].b),now++;
			int tmp=dsu.top;
			for(int x=(i-1)*len+1; x<=i*len; x++){
				if(x>m) break;
				if(e[x].a<=t.a&&e[x].b<=t.b) 
					dsu.merge(e[x].x,e[x].y,e[x].a,e[x].b);
			}
			bool flag=1;
			int x=dsu.find(t.x),y=dsu.find(t.y);
			if(x!=y) flag=0;
			if(dsu.mx_a[x]!=t.a||dsu.mx_b[x]!=t.b) flag=0;
			ans[t.id]=flag;
			while(dsu.top>tmp) dsu.del();
		}
	}
	for(int i=1; i<=k; i++) 
		if(ans[i]) cout << "Yes" << endl;
		else cout << "No" << endl;
	return 0;
}
posted @ 2025-02-18 09:16  lastxuans  阅读(15)  评论(0)    收藏  举报