整体二分

概述

通过二分值域,将询问转化为判定,将值域递归分解

条件

  • 可离线
  • 答案可二分
  • 修改对判定答案的贡献相对独立,修改之间不互相影响
  • 修改操作对其影响的询问的贡献情况不随判定标准的改变而改变

思路

将所有操作离线,枚举值域的mid,判断该条操作属于哪半部分,进行规模减半的递归

例题1(洛谷2617

n个数的序列 a,支持两种操作:

Q , l , r , k 下标区间在\([l,r]\)中的第k小

C x y 将 \(a_x\) 修改为 y

起始数组转化为 n 次插入,修改 转化为 删除原数+插入新数

对于询问区间第k大,mid为值域L~R的中值,小于mid的数=1,大于mid的数=0

数状数组查询询问\([x,y]\)区间的1的个数,若k<=num,该询问的答案在L~mid,反之在 mid+1~ R \(k-=num\)

具体细节见代码

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=1e5+10;
#define lowbit(x) ((-x)&x)
#define val(x) (x<0?-1:1)
struct aaa{
	int x,y,k,id;
	//x~y的第k大,该操作答案为id;
	//y=-1 第x个数,插入/删除
}q[N<<2],tmp[N<<2];
int a[N],id,ans[N<<2],tr[N<<2];
//数状数组统计L-mid区间内的数的分布情况
void cg(int x,int val){ while(x<=id)tr[x]+=val,x+=lowbit(x);}
int getsum(int x)
{
	int res=0;
	while(x) res+=tr[x],x-=lowbit(x);
	return res;
}

void sol(int l,int r,int L,int R)//第l~r个询问,值域在L~R之间
{
	if(l>r) return;
	if(L==R)//相等 中间的询问都等于L
	{
		per(i,l,r) if(q[i].y!=-1) ans[q[i].id]=L;
		return;
	}
	int mid=(L+R)>>1,p1=l-1,p2=r+1,num;//按值域二分
	per(i,l,r)
	{
		if(q[i].y==-1)//y=-1 表示修改
		{
			if(abs(q[i].k)<=mid)/*按值域二分,所以看修改的值是否<=mid*/ cg(q[i].x,val(q[i].k)),tmp[++p1]=q[i];
			else tmp[--p2]=q[i];//小于mid,change
		}
		else
		{
			num=getsum(q[i].y)-getsum(q[i].x-1);
			if(q[i].k<=num) tmp[++p1]=q[i];//若k<=num表示答案在L~mid区间,因为只有L~mid区间的修改被操作
			else q[i].k-=num,tmp[--p2]=q[i];//第k小是相对区间而言的,减去num
		}
	}
	per(i,l,p1)
	{
		q[i]=tmp[i];//把左右区间放回q,清空数状数组(循环比menset快)
		if(q[i].y==-1&&abs(q[i].k)<=mid) cg(q[i].x,-val(q[i].k));
	}
	per(i,p2,r)
	{
		q[i]=tmp[r+p2-i];//同上,因为右区间只知道右端点,所以用p2转换
		if(q[i].y==-1&&abs(q[i].k)<=mid) cg(q[i].x,-val(q[i].k));
	}
	sol(l,p1,L,mid),sol(p2,r,mid+1,R);
}
signed main()
{
	int n,m,x,y,k;
	char ch;
	cin>>n>>m;
	per(i,1,n)
	{
		scanf("%d",a+i);
		q[++id]=(aaa){i,-1,a[i],id};//数组转换成添加
	}
	per(i,1,m)
	{
		cin>>ch;
		if(ch=='Q')
		{
			scanf("%d%d%d",&x,&y,&k);
			q[++id]=(aaa){x,y,k,id};//询问
		}
		else 
		{
			scanf("%d%d",&x,&y);//修改转化为先删除再添加
			q[++id]=(aaa){x,-1,-a[x],id},q[++id]=(aaa){x,-1,y,id};
			a[x]=y;//记得修改a[x],方便下次修改(-a[x])
		}
	}
	memset(ans,-1,sizeof(ans));
	sol(1,id,0,1e9);
	per(i,1,id) if(ans[i]!=-1) printf("%d\n",ans[i]);
	return 0;
}

例题2(洛谷7424

每个木板被S颗子弹穿过之后会被击碎

给定每颗子弹射出的位置,和木板的位置

问每颗子弹射出之后击碎几块木板

转换为问 每个木板 在 第几颗子弹 射出后被击碎

值域区间为子弹射出顺序m,如果在L~mid颗子弹内碎了,k<=num 左区间,反之,右区间

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=2e5+10;
#define lowbit(x) ((-x)&x)
struct aaa{
	int x,y,k,t,id;
}q[N<<1],tmp[N<<1];
int l[N],r[N],v[N],id,ans[N],tr[N<<2];
//数状数组统计L-mid区间内的数的分布情况
void cg(int x,int val){ while(x<=id)tr[x]+=val,x+=lowbit(x);}
int getsum(int x)
{
	int res=0;
	while(x) res+=tr[x],x-=lowbit(x);
	return res;
}

void sol(int l,int r,int L,int R)//第l~r个询问,值域在L~R之间
{
	if(l>r) return;
	if(L==R)//相等 木板都被编号为L的子弹击碎
	{
		per(i,l,r) if(q[i].t) ans[L]++;
		return;
	}
	int mid=(L+R)>>1,p1=l-1,p2=r+1,num;
	per(i,l,r)
	{
		if(q[i].t)//木板
		{
			num=getsum(q[i].y)-getsum(q[i].x-1);
			if(q[i].k<=num) tmp[++p1]=q[i];//左区间
			else q[i].k-=num,tmp[--p2]=q[i];//右区间
		}
		else//子弹
		{
			if(q[i].id<=mid)
			{
				cg(q[i].x,1);
				tmp[++p1]=q[i];
			}
			else tmp[--p2]=q[i];
		}
	}
	per(i,l,p1)
	{
		q[i]=tmp[i];
		if(!q[i].t) cg(q[i].x,-1);
	}
	per(i,p2,r) q[i]=tmp[r+p2-i];
	sol(l,p1,L,mid),sol(p2,r,mid+1,R);
}
signed main()
{
	int n,m,x,y,k;
	cin>>n>>m;
	per(i,1,n) scanf("%d%d%d",l+i,r+i,v+i);
	per(i,1,m)
	{
		scanf("%d",&x);
		q[++id]=(aaa){x,0,0,0,i};//x位置射出,t=0
	}
	per(i,1,n) q[++id]=(aaa){l[i],r[i],v[i],1,i};//区间l~r,S,t=1
	sol(1,id,1,m+1);
	per(i,1,m) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2023-02-11 16:40  f2021yjm  阅读(18)  评论(0编辑  收藏  举报