整体二分学习笔记

整体二分算是一个比较重要的算法,我个人看来也挺难的(主要是我菜)
目前也写了三道模板题,来小小的总结一下

整体二分的适用情况

当题目的询问需要进行二分,且有多组操作的时候(包括赋值操作),我们可以同时对所有操作二分,每次将左区间的贡献加到右区间继续分治下去即可,主体架构很像CDQ分治
当然也可以用一些大数据结构写(明示树套树)

算法流程

我们首先确定范围,答案的值域和所有询问
对于每次二分到的mid,我们对于不同的要求,把mid左边的贡献统计出来
对于当前区间所有操作判断,如果合法,放进左边的数组,不然放进右边数组,同时把左边的贡献存进一个随时间更新的数组里,因为接下来他们二分的值域区间是 [ m i d + 1 , r ] [mid+1,r] [mid+1,r],不会包含 [ l , m i d ] [l,mid] [l,mid]区间的贡献,然后清空当前的状态量,分为左右数组继续分治,如果 l = = r l==r l==r,即找到可行解,统计答案。

接下来是三道题目

洛谷P3527 [POI2011]MET-Meteors
这一题显然对于每一个点,进行二分是最好的,但是它又要求多组解
那么我们整体二分,这里的值域是k个陨石,问题是n个王国
我们维护一个时间戳,每次暴力把当前状态更新到mid处,想一下,如果每次清空的话,那么每次二分都回覆盖 [ l , m i d ] [l,mid] [l,mid],是会超时的,但是这样每次更新只会有半个区间长,更快
因为是区间加,所以要差分,但又因为是环,我们要特殊判断

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long lll;
const int maxn = 300007;
const int INF = 2147483647;
long long tr[maxn],val[maxn];
int n,m,k;


vector<int>cont[maxn];


int ans[maxn];
int ll[maxn],rr[maxn],num[maxn],t;
int lt[maxn],rt[maxn],pos[maxn],want[maxn];

int lowbit(int x){return x&-x;}
void jb(int x,int num){
	for(;x<=m;x+=lowbit(x))
	tr[x]+=num;
}

lll que(int x){
	lll res=0;
	for(;x;x-=lowbit(x))res+=tr[x];
	return res;
}

void change(int l,int r,int num){
	if(l<=r){
		jb(l,num),jb(r+1,-num);
	}
	else{
		jb(l,num),jb(m+1,-num);
		jb(1,num),jb(r+1,-num);
	}
}
void query(int l,int r,int p1,int p2){
	if(l>r)return;if(p1>p2)return;
	if(l==r){
		for(int i=p1;i<=p2;i++){
			ans[pos[i]]=l;
		}
		return ;
	}
	int mid=(l+r)>>1;
	while(t<mid)++t,change(ll[t],rr[t],num[t]);
	while(t>mid)change(ll[t],rr[t],-num[t]),t--;
	
	for(int i=p1;i<=p2;i++){
		val[pos[i]]=0;
		for(int j=0;j<cont[pos[i]].size();j++){
			val[pos[i]]+=que(cont[pos[i]][j]);
			if(val[pos[i]]>=want[pos[i]])break;
		}
	}
	int ls=0,rs=0;
	for(int i=p1;i<=p2;i++){
		if(val[pos[i]]>=want[pos[i]]){
			lt[++ls]=pos[i];
		}
		else rt[++rs]=pos[i];
	}
	int mid2=ls;
	for(int i=1;i<=ls;i++)pos[i+p1-1]=lt[i];
	for(int i=1;i<=rs;i++)pos[i+p1+ls-1]=rt[i];
	query(l,mid,p1,p1+mid2-1);
	query(mid+1,r,p1+mid2,p2);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x;
		scanf("%d",&x);
		cont[x].push_back(i);
	}
	for(int i=1;i<=n;i++)
		scanf("%d",&want[i]),pos[i]=i;
	scanf("%d",&k);
	for(int i=1;i<=k;i++){
		scanf("%d%d%d",&ll[i],&rr[i],&num[i]);
	}
	++k,ll[k]=1,rr[k]=m;num[k]=INF;
	query(1,k,1,n);
	for(int i=1;i<=n;i++){
		if(ans[i]==k){
			printf("NIE\n");
		}
		else printf("%d\n",ans[i]);
	}
	return 0;
}

洛谷P1527 [国家集训队]矩阵乘法
这题要去二维区间第K大,我们使用树套树当然是没有问题的,但是难写啊!
我们还是使用整体二分,我们将所有点,记录在一个结构体里,存下 ( x , y ) (x,y) (x,y) v a l val val
然后我们开始二分
我们先开始染色,他要求的是二维区间第K大,那么对于我们二分的mid,如果任务区间内
大于它的数有k-1个,不就正好是答案了吗,那么我们先对 [ l , m i d ] [l,mid] [l,mid]染色,全部变成1
这里我们使用了二维树状数组,然后判断放进左右数组的时候,我们树状数组二维区间求和即可
判断所求值和目标排名的关系即可
注意这次树状数组就要清空了,但是切记不能用memset,怎么加的怎么删就行

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;

const int maxn = 507;
const int N = 60007;
int lowbit(int x){return x&-x;}
struct number{
	int x,y,v;
	friend bool operator <(number a,number b){return a.v<b.v;}
}num[maxn*maxn];

struct task{int f1,f2,f3,f4,k;}Q[N];


int id[N],s1[N],s2[N];
int n,q,tr[maxn][maxn];

void add(int x,int y,int val){
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=n;j+=lowbit(j))
			tr[i][j]+=val;
}
int queryPoint(int x,int y){
	int ret=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			ret+=tr[i][j];
	return ret;
}

int querySection(int f1,int f2,int f3,int f4){
	return queryPoint(f1-1,f2-1)
		  +queryPoint(f3,f4)
		  -queryPoint(f1-1,f4)
		  -queryPoint(f3,f2-1);
}

int cur[N],ans[N];

void solve(int l,int r,int L,int R){
	if(R<L)return;
	if(l==r){
		for(int i=L;i<=R;i++)ans[id[i]]=num[l].v;
		return;
	}
	int mid=l+r>>1;
	for(int i=l;i<=mid;i++)add(num[i].x,num[i].y,1);
	int S1=0,S2=0;
	for(int i=L;i<=R;i++){
		int u=id[i];
		int sum=cur[u]+querySection(Q[u].f1,Q[u].f2,Q[u].f3,Q[u].f4);
		if(sum>=Q[u].k) s1[++S1]=u;
		else s2[++S2]=u,cur[u]=sum;
	}
	int start=L-1;
	for(int i=1;i<=S1;i++)id[++start]=s1[i];
	for(int i=1;i<=S2;i++)id[++start]=s2[i];
	for(int i=l;i<=mid;i++)add(num[i].x,num[i].y,-1);
	solve(l,mid,L,L+S1-1);solve(mid+1,r,L+S1,R);
}
int temp=0;
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			int x;scanf("%d",&x);
			num[++temp]=(number){i,j,x};
		}
	sort(num+1,num+1+temp);
	for(int i=1;i<=q;i++)id[i]=i,scanf("%d%d%d%d%d",&Q[i].f1,&Q[i].f2,&Q[i].f3,&Q[i].f4,&Q[i].k);
	solve(1,temp,1,q);
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
}

最后这一题有点难
洛谷P3332 [ZJOI2013]K大数查询
这一题比较艹,因为有修改,然而实际上问题并不大,我们要求的还是区间第K大
但是这次是一维数列还有区间加,好像必须树套树了耶。。。
我会屈服吗?不会,为此我特地去学了超级树状数组,发现出人意料的好写…

回归题目,我们考虑在分治过程中遇到区间加操作的怎么办
我们考虑值域范围内二分出来的mid,如果区间加的C大于mid,我们就统计
将任务区间全部加一,并放到右区间
不然就不动,放到左区间

我们考虑为什么,当时我想了很久,这不是一点不考虑以前的贡献吗?
然后我发现我傻了,因为我tm看错题了,看完样例解析,我豁然开朗
原来加一个数的意思就是,当前位置多一个数,当前位置可以有很多数!

那么这题一下子就简单了,上面的操作原因也就很显然了,因为C小于mid时
是有可能对左区间产生贡献的,所以放下去继续二分
但是大于mid时,就只会对右边产生贡献了,我们往右边继续分治,但是要更新右区间的目标值.
即减去之前的区间加对他的贡献

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 50007;
typedef long long ll;
const int N = 200000;

int lowbit(int x){return x&-x;}

struct node{
	int tp,a,b,id;
	ll c;
}tk[maxn],s1[maxn],s2[maxn];
int out[maxn],n,m;
ll tr1[maxn],tr2[maxn];

void add(ll tr[],int x,int num){
	for(;x<=n;x+=lowbit(x))
		tr[x]+=num;
}

ll query(ll tr[],int x){
	ll ret=0;
	for(;x;x-=lowbit(x))ret+=tr[x];
	return ret;
}

void solve(int l,int r,int L,int R){
	if(L>R)return;
	if(l==r){
		for(int i=L;i<=R;i++)
			out[tk[i].id]=l;
		return;
	}
	int mid=l+r>>1;int S1=0,S2=0;
	for(int i=L;i<=R;i++){
		if(tk[i].tp==2){
			ll sum=tk[i].b*query(tr1,tk[i].b) - query(tr2,tk[i].b) - (tk[i].a-1)*query(tr1,tk[i].a-1) + query(tr2,tk[i].a-1);
			if(sum<tk[i].c){
				tk[i].c-=sum;
				s1[++S1]=tk[i];
			} else s2[++S2]=tk[i];
		}
		else{
			if(tk[i].c>mid){
				add(tr1,tk[i].a,1),add(tr1,tk[i].b+1,-1);
				add(tr2,tk[i].a,tk[i].a-1),add(tr2,tk[i].b+1,-tk[i].b);
				s2[++S2]=tk[i];
			} else s1[++S1]=tk[i];
		}
	}
	for(int i=L;i<=R;i++){
		if(tk[i].tp==1&&tk[i].c>mid){
			add(tr1,tk[i].a,-1),add(tr1,tk[i].b+1,1);
			add(tr2,tk[i].a,-tk[i].a+1),add(tr2,tk[i].b+1,tk[i].b);
				
		}
	}
	int mxt=L-1;
	for(int i=1;i<=S1;i++)tk[++mxt]=s1[i];
	for(int i=1;i<=S2;i++)tk[++mxt]=s2[i];
	solve(l,mid,L,L+S1-1);
	solve(mid+1,r,L+S1,R);
}
int mxt;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%lld",&tk[i].tp,&tk[i].a,&tk[i].b,&tk[i].c);
		if(tk[i].tp==2)tk[i].id=++mxt;
	}
	solve(-n,n,1,m);
	for(int i=1;i<=mxt;i++)
		printf("%lld\n",out[i]);
	return 0;
}

好像模板的就这三题,我刷题量还是太少了嘤嘤嘤
还有一些多组询问的也可以用整体二分写,不过我还是喜欢写BIT套主席树
比如洛谷P2617 Dynamic Rankings

我 永 远 喜 欢 主 席 树 . j p g ! ! ! 我永远喜欢主席树.jpg!!! .jpg在这里插入图片描述

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信博6主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值