(笔记)整体二分

整体二分能解决的问题一般具有如下特点:

  1. 询问的答案具有可二分性。
  2. 修改对判定答案的贡献互相独立,修改之间互不影响效果。
  3. 修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值。
  4. 贡献满足交换律,结合律,具有可加性。
  5. 题目允许使用离线算法。

——许昊然《浅谈数据结构题几个非经典解法》

基本思路

设想对询问答案进行分治,如果每层每个节点都要进行 \(O(n)\) 次操作分治复杂度达到了 \(O(n^2\log n)\) ,还不如不分。反之,如果是每层都只要进行 \(O(n)\) 次操作,那么总体复杂度直接变成了 \(O(n\log n)\),优于上者。整体二分就是采用这种策略将分治时间复杂度简化。

具体地,将每次的询问离线下来,在每个分治点上划分为 \([l,mid]\)\([mid+1,r]\) 两部分,节点内用 \(siz=r-l+1\)\(O(siz)\) 次操作完成划分和答案查询,在 \(l=r\) 时记录答案即可。

例题

P1527 [国家集训队] 矩阵乘法

思路

本题可以采用二维树状数组,每个分治点将 \(\le mid\) 的数赋值为 \(1\) 并根据询问子矩阵内 \(1\) 的个数 \(\geq k\)\(< k\) 划分询问。为什么这样做是对的?

考虑二分过程中的每一层对答案的贡献。

  1. 对于每一层二分,矩阵中的每个元素最多被加入树状数组一次。
  2. 对于每一层二分,每个询问只会被处理一次。
  3. 二分值域的过程中最多只会出现 \(O(\log n)\) 层。

符合我们对整体二分的思路要求。树状数组每层操作数为 \(O(n^2\log^2 n)\) ,查询数为 \(O(q\log^2 n)\),分治层数为 \(O(\log n)\),那么总体时间复杂度为 \(O((n^2+q)\log ^3 n)\),需要注意常数优化。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N=505,M=2.5e5+5;
int n,q,cnt,lsh[M];
int a[N][N];
vector<PII>G[M];
struct Tre{
	int av[N][N];
	int lowbit(int x){return x&-x;}
	void ins(int x,int y,int z){
		for(int i=x;i<=n;i+=lowbit(i))
			for(int j=y;j<=n;j+=lowbit(j))
				av[i][j]+=z;
	}
	int que(int x,int y){
		int res=0;
		for(int i=x;i;i-=lowbit(i))
			for(int j=y;j;j-=lowbit(j))
				res+=av[i][j];
		return res;
	}
}T;
struct Node{int xa,ya,xb,yb,k,id;};
vector<Node>Q;
int ans[M];
int query(Node x){
	int xa=0,ya=0,xb=0,yb=0;
	xa=x.xa;ya=x.ya;xb=x.xb;yb=x.yb;
	int res=T.que(xb,yb)+T.que(xa-1,ya-1);
	res-=T.que(xa-1,yb)+T.que(xb,ya-1);
	return res;
}
void fz(int l,int r,vector<Node>&vec){
	if(!vec.size())return ;
	if(l==r){
		for(Node i:vec)ans[i.id]=lsh[l];
		return ;
	}
	int mid=(l+r)>>1;
	vector<Node>vec0,vec1;
	for(int i=l;i<=mid;i++)
		for(PII j:G[i]){
			int x=j.first,y=j.second;
			T.ins(x,y,1);
		}
	for(Node i:vec){
		int val=query(i);
		if(val>=i.k)vec0.push_back(i);
		else i.k-=val,vec1.push_back(i);
	}
	for(int i=l;i<=mid;i++)
		for(PII j:G[i]){
			int x=j.first,y=j.second;
			T.ins(x,y,-1);
		}
	fz(l,mid,vec0);fz(mid+1,r,vec1);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
			lsh[++cnt]=a[i][j];
		}
	for(int i=1;i<=q;i++){
		int xa,ya,xb,yb,k;
		cin>>xa>>ya>>xb>>yb>>k;
		Q.push_back((Node){xa,ya,xb,yb,k,i});
	}
	sort(lsh+1,lsh+1+cnt);
	cnt=unique(lsh+1,lsh+1+cnt)-(lsh+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			a[i][j]=lower_bound(lsh+1,lsh+1+cnt,a[i][j])-lsh;
			G[a[i][j]].push_back(make_pair(i,j));
		}
	fz(1,cnt,Q);
	for(int i=1;i<=q;i++)
		cout<<ans[i]<<'\n';
	return 0;
}

P11295 [NOISG 2022 Qualification] Dragonfly

部分整体二分需要在二分时预先处理 \([1,mid]\) 这个区间的数据,所以需要在分治时先递归右区间然后删除 \([L,mid]\) 的贡献,再递归左区间,本题就是典例。

注意到每个点能贡献的时间区间一定是一个前缀 \([1,val_i]\) 考虑到先通过整体二分预处理出这个 \(val_i\),然后直接套用离线二维数点计算答案即可。

时间复杂度 \(O(d\log d\log n+d\log n)\)

由于写法常数实在是太大了,荣获 97pts TLE on Subtask #7,不改了,仅供参考。

UPD:哈哈发现整体二分中间有个 \(\log ^2\) 的查询,直接飙升为 \(O(d\log d\log^2 n)\),现在改好了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+5,D=2e6+5;
int n,d;
struct Tre{
	LL av[N];
	int lowbit(int x){return x&-x;}
	void ins(int p,LL x){for(int i=p;i<=n;i+=lowbit(i))av[i]+=x;}
	LL que(int p){LL res=0;for(int i=p;i;i-=lowbit(i)){res+=av[i];}return res;}
}T;
int b[N],s[N],h[D],val[N];
vector<int>G[N];
int dfn[N],top[N],fat[N],tms;
int son[N],siz[N];
void dfs0(int u,int fa){
	fat[u]=fa;
	siz[u]=1;
	for(int v:G[u]){
		if(v==fa)continue;
		dfs0(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])
			son[u]=v;
	}
}
void dfs1(int u){
	dfn[u]=++tms;
	int fa=fat[u];
	if(son[fa]==u)top[u]=top[fa];
	else top[u]=u;
	if(son[u])dfs1(son[u]);
	for(int v:G[u]){
		if(v==son[u]||v==fa)continue;
		dfs1(v);
	}
}
int query(int x){
	int res=0;
	while(x){
		res+=T.que(dfn[x])-T.que(dfn[top[x]]-1);
		x=fat[top[x]];
	}
	return res;
}
int M[N],A[N],B[N];
void solve(int l,int r,int L,int R){
	if(L==R){
		for(int i=l;i<=r;i++){
			int v=M[i];
			val[v]=L;
		}
		return ;
	}
	int mid=(L+R)>>1;
	for(int i=L;i<=mid;i++)
		T.ins(dfn[h[i]],1);
	int cnta=0,cntb=0;
	for(int i=l;i<=r;i++){
		int v=M[i];
		if(b[v]-(T.que(dfn[v]+siz[v]-1)-T.que(dfn[v]-1))>0)B[++cntb]=v;
		else A[++cnta]=v;
	}
	for(int i=l;i<=r;i++){
		if(i-l+1<=cnta)M[i]=A[i-l+1];
		else M[i]=B[i-l-cnta+1];
	}
	if(cntb)solve(l+cnta,r,mid+1,R);
	for(int i=L;i<=mid;i++)
		T.ins(dfn[h[i]],-1);
	if(cnta)solve(l,l+cnta-1,L,mid);
}
int now[N];
vector<int>opt[D];
void dfs2(int u){
	int fa=fat[u],tp=now[s[u]];
	if(now[s[u]]){
		if(val[tp]<val[u]){
			now[s[u]]=u;
			opt[val[tp]+1].push_back(u),opt[val[u]+1].push_back(-u);
		}
	}
	else now[s[u]]=u,opt[1].push_back(u),opt[val[u]+1].push_back(-u);
	for(int v:G[u]){
		if(v==fa)continue;
		dfs2(v);
	}
	if(now[s[u]]==u)now[s[u]]=tp;
}
int main(){
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	for(int i=1;i<=d;i++)scanf("%d",&h[i]);
	for(int i=1;i<n;i++){
		int u,v;scanf("%d%d",&u,&v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs0(1,0);
	dfs1(1);
	for(int i=1;i<=n;i++)
		M[i]=i;
	solve(1,n,1,d);
	for(int i=1;i<=n;i++)
		if(!b[i])val[i]=0;
	dfs2(1);
	for(int i=1;i<=d;i++){
		for(int v:opt[i]){
			if(v>0)T.ins(dfn[v],1);
			else T.ins(dfn[-v],-1);
		}
		printf("%d ",query(h[i]));
	}
	return 0;
}
posted @ 2025-04-24 14:43  TBSF_0207  阅读(33)  评论(0)    收藏  举报