整体二分专题

整体二分一般解决:边修改边询问,询问一般询问区间[l,r]里第k小/大

思想:整体二分相当于对所有询问与修改一起二分,二分的是那个第k大/小

解决办法:solve(l,r,L,R),表示处理位于(l,r)(这里的l,r,并不是原询问顺序,而是将询问排序到这一层时的l,r具体理解看下面)中的询问,L,R是二分的范围。

首先,当L=R时,将l,r内的所有答案赋为L

当L!=R;

int md=L+(R-L)/2

对于会影响到md+/-的修改,将他的影响“体现”出来(比如二分了md,问的是第k小,如果增加一个小于md的值,那么就在树状数组(只是一种表现形式)里这个位置+1)然后将这个修改放在左/右半边,否则放在右/左半边。

对于已经满足条件的询问(比如这个区间里的值大于等于这个询问里的k),将它归到左/右半边去,否则k减去当前区间里的值(因为这些值是由满足<=md的询问产生的,它们都会被归到左/右边去,在下一层的右/左半边都不会再被算,但其实对这个询问有影响,所以要把这些值算进去),然后归到右/左半边。

重新排序,按照原来时序排左边,再按原来时序排右边,将两者合并,得到新的序列

将“体现”出来的影响消去。

最后递归solve(l,l+左半边个数-1,L,md),solve(l+左半边个数,r,md+1,R)。

下帖例题

bzoj2527: [Poi2011]Meteors

这个题要注意是一个环,所以当跨n时要特殊处理

体现影响的容器选用树状数组即可,每次插入使用差分的思想,恰好树状数组是求前缀和

这里的询问不具有时序,而流星雨场数恰好就是我们要求得,所以我们可以只二分询问,对于每个md直接暴力的把小于md的流星雨加进来,大于的减去即可。

#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N=300050;
int n,m;
long long  c[N];
int cn=0;
int head[N],to[N],next[N];
void add1(int f,int t)
{
	to[++cn]=t;
	next[cn]=head[f];
	head[f]=cn;
}
int lowbit(int x)
{
	return x&(-x);
}
void plu(int pos,long long x)
{
	while(pos<=m)
		c[pos]+=x,pos+=lowbit(pos);
}
long long sum(int end)
{
	long long sum1=0;
	while(end>0)
		sum1+=c[end],end-=lowbit(end);
	return sum1;
}
struct caozuo
{
	int l,r;
	long long x;
}T[N];
void add(caozuo a,int xx)
{
	if(a.l<=a.r)
		plu(a.l,a.x*xx),plu(a.r+1,(0LL-a.x)*xx);
	else
		plu(a.l,a.x*xx),plu(1,a.x*xx),plu(a.r+1,(0LL-a.x)*xx);
}
int cnt,t,ans[N],id[N];
int a[N],o[N],tmp[N],mark[N];
void solve(int l,int r,int L,int R)
{
	if(l>r)
		return ;
	if(L==R)
	{
		for(int i=l;i<=r;i++)
			ans[id[i]]=L;
		return;
	}
	int mid=(L+R)/2;
	while(cnt<=mid)	add(T[++cnt],1);
	while(cnt>mid)	add(T[cnt--],-1);
	int cnt1=0;
	for(int i=l;i<=r;i++)
	{
		long long tot=0;
		int now=id[i];
		for(int j=head[now];j!=-1;j=next[j])
		{
			tot+=sum(to[j]);
			if(tot>=a[now])
				break;
		}
		if(tot>=a[now])
			mark[now]=1,cnt1++;
		else
			mark[now]=0;
	}
	int l1=l,l2=l+cnt1;
	for(int i=l;i<=r;i++)
	{
		if(mark[id[i]])	tmp[l1++]=id[i];
		else tmp[l2++]=id[i];
	}
	for(int i=l;i<=r;i++)
		id[i]=tmp[i];
	solve(l,l1-1,L,mid);
	solve(l1,l2-1,mid+1,R);
}
int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d",&o[i]),add1(o[i],i);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]),id[i]=i;
	scanf("%d",&t);
	for(int i=1;i<=t;i++)
		scanf("%d%d%lld",&T[i].l,&T[i].r,&T[i].x);
	T[++t].l=1,T[t].r=m,T[t].x=1000000000LL;
	solve(1,n,1,t);
	for(int i=1;i<=n;i++)
	{
		if(ans[i]==t)
			printf("NIE\n");
		else
			printf("%d\n",ans[i]);
	}
}

bzoj3110: [Zjoi2013]K大数查询

在一段里每个位置加上一个数,每次问的是一个区间,所以影响体现容器就用一棵线段树即可。

这里修改与询问都具有时序,所以就老老实实的按开头说的那样写就好了

#include<iostream>
#include<math.h> 
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<vector>
#define lson rt*2
#define rson rt*2+1
#define mid (l+r)/2
using namespace std;
const int N=500050;
typedef long long ll;
vector<int> lp,rp;
struct node
{
	ll sum,flag;
}tree[N*4];
void pushup(int rt)
{
	tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void pushdown(int rt,int l,int r)
{
	if(l==r)
		return ;
	if(tree[rt].flag)
	{
		tree[lson].sum+=tree[rt].flag*(long long)(mid-l+1);
		tree[rson].sum+=tree[rt].flag*(long long)(r-mid);
		tree[lson].flag+=tree[rt].flag;
		tree[rson].flag+=tree[rt].flag;
		tree[rt].flag=0LL;
	}
}
void ins(int rt,int l,int r,int L,int R,long long vl)
{
	if(l==L&&r==R)
	{
		tree[rt].sum+=(ll)(r-l+1)*vl;
		tree[rt].flag+=vl;
		return ;
	}
	pushdown(rt,l,r);
	if(mid>=R)
		ins(lson,l,mid,L,R,vl);
	else if(mid<L)
		ins(rson,mid+1,r,L,R,vl);
	else 
		ins(lson,l,mid,L,mid,vl),ins(rson,mid+1,r,mid+1,R,vl);
	pushup(rt);
}
long long query(int rt,int l,int r,int L,int R)
{
	if(l==L&&r==R)
		return tree[rt].sum;
	pushdown(rt,l,r);
	if(mid>=R)
		return query(lson,l,mid,L,R);
	else if(mid<L)
		return query(rson,mid+1,r,L,R);
	else 
		return query(lson,l,mid,L,mid)+query(rson,mid+1,r,mid+1,R);
}
struct que
{
	int l,r;
	ll value;
	int lei;
}q[N];
int ans[N],m,n;
int id[N],maxv=-500050,minv=500050;
void solve(int l,int r,int L,int R)
{
	if(l>r)
		return ;
	if(L==R)
	{
		for(int i=l;i<=r;i++)
			if(q[id[i]].lei==2)
				ans[id[i]]=L;
		return ;
	}
	int md=L+(R-L)/2;
	for(int i=l;i<=r;i++)
	{
		if(q[id[i]].lei==1)
		{
			if(q[id[i]].value>md)	ins(1,1,n,q[id[i]].l,q[id[i]].r,1),rp.push_back(id[i]);
			else lp.push_back(id[i]);
		}
		else
		{
			ll c=query(1,1,n,q[id[i]].l,q[id[i]].r);
			if(c>=q[id[i]].value)
				rp.push_back(id[i]);
			else
				lp.push_back(id[i]),q[id[i]].value-=c;
		}
	}
	for(int i=l;i<=r;i++)
		if(q[id[i]].lei==1)
			if(q[id[i]].value>md)	
				ins(1,1,n,q[id[i]].l,q[id[i]].r,-1);
	int x=lp.size();
	for(int i=0;i<lp.size();i++)
		id[i+l]=lp[i];
	for(int i=0;i<rp.size();i++)
		id[i+l+x]=rp[i];
	lp.clear(),rp.clear();	
	solve(l,l+x-1,L,md);
	solve(l+x,r,md+1,R);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d%lld",&q[i].lei,&q[i].l,&q[i].r,&q[i].value),id[i]=i,maxv= q[i].lei==1?max(maxv,(int)q[i].value):maxv,minv= q[i].lei==1?min(minv,(int)q[i].value):minv;
	solve(1,m,minv,maxv);
	for(int i=1;i<=m;i++)
		if(q[i].lei==2)
			printf("%d\n",ans[i]);
}

bzoj2738: 矩阵乘法

嗯矩阵乘法,得先写个模板,再写快速幂,准备好纸笔准备推矩阵,然后发现

给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数。”。。。。。。。。。。。

这个题所有的“修改”只有开始的赋值。相当于所有修改没时序,直接对所有询问二分就好了

#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N=550;
const int Q=60050;
int n,h,ans[Q],qq;
struct aa
{
	int vl,x,y;
}a[N*N];
int cmp(aa a1,aa a2)
{
	return a1.vl<a2.vl;
}
struct que
{
	int x1,x2,y1,y2;
	long long k;
}q[Q];
long long c[N][N];
int lowbit(int x)
{
	return x&(-x);
}
void plu(int x,int y,long long  val)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=n;j+=lowbit(j))
			c[i][j]+=val;
}
long long  query(int end1,int end2)
{
	long long sum=0;
	for(int i=end1;i>0;i-=lowbit(i))
		for(int j=end2;j>0;j-=lowbit(j))
			sum+=c[i][j];
	return sum;
}
int cnt,tot;
void add(aa x,int f)
{
	plu(x.x,x.y,(long long)f);
}
long long qiu(int x1,int y1,int x2,int y2)
{
	long long sum=0;
	sum+=query(x1-1,y1-1);
	sum-=query(x1-1,y2);
	sum-=query(x2,y1-1);
	sum+=query(x2,y2);
	return sum;
}
int id[N*N],tmp[N*N];
void solve(int l,int r,int L,int R)
{
	if(l>r)
		return ;
	if(L==R)
	{
		for(int i=l;i<=r;i++)
			ans[id[i]]=L;
		return ;
	}
	int md=L+(R-L)/2;
	while(a[cnt+1].vl<=md&&cnt<h) add(a[++cnt],1);
	while(a[cnt].vl>md) add(a[cnt--],-1);
	int cn1=0;
	for(int i=l;i<=r;i++)
		if(qiu(q[id[i]].x1,q[id[i]].y1,q[id[i]].x2,q[id[i]].y2)>=q[id[i]].k)
			cn1++;
	int l1=l,l2=l+cn1;
	for(int i=l;i<=r;i++)
	{
		if(qiu(q[id[i]].x1,q[id[i]].y1,q[id[i]].x2,q[id[i]].y2)<q[id[i]].k)
			tmp[l2++]=id[i];
		else
			tmp[l1++]=id[i];
	}
	for(int i=l;i<=r;i++)
		id[i]=tmp[i];	
	solve(l,l1-1,L,md);
	solve(l1,l2-1,md+1,R);
}
int minx=1000000000,maxx=0;
int main()
{
	scanf("%d%d",&n,&qq);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&a[++h].vl),a[h].x=i,a[h].y=j,maxx=max(maxx,a[h].vl),minx=min(minx,a[h].vl);
	sort(a+1,a+h+1,cmp);
	for(int i=1;i<=qq;i++)
		scanf("%d%d%d%d%d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2,&q[i].k),id[i]=i;
	solve(1,qq,minx,maxx+1);
	for(int i=1;i<=qq;i++)
		printf("%d\n",ans[i]);
}

bzoj1901: Zju2112 Dynamic Rankings

这个题很妙(或者说是在下太蠢)
同一个修改是修改一个值,如果单把他当作一个询问来做,当你修改一个值时,可能既对<=md的产生影响,也对>md的产生影响,该把它归到那一边,体不体现它的影响,怎么体现,成了一个个难题。
所以开始先模拟一边所有的修改,把一次修改当作两个时序相连的操作,1减少了哪个值,2增加了哪个值,任意一个操作只有一个“值性”,所以便可以整体二分了
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N=30050;
int n,m;
int a[N],c[N];
struct que
{
	int l,r,k,lei;
}q[N];
char op;
int cnt;
int lowbit(int x)
{
	return x&(-x);
}
void plu(int pos,int x)
{
	while(pos<=n)
		c[pos]+=x,pos+=lowbit(pos);
}
int get(int end)
{
	int sum=0;
	while(end>0)
		sum+=c[end],end-=lowbit(end);	
	return sum;
}
int query(que a)
{
	return get(a.r)-get(a.l-1);
}
int tmp[N];
int zhuan[N],id[N],ans[N];
bool mark[N];
void solve(int l,int r,int L,int R)
{
	if(l>r)
		return ;
	if(L==R)
	{
		for(int i=l;i<=r;i++)
			if(q[id[i]].lei==3)
				ans[id[i]]=L;
		return ;
	}
	int md=L+(R-L)/2;
	for(int i=l;i<=r;i++)
	{
		if(q[id[i]].lei==1&&q[id[i]].k<=md) plu(q[id[i]].l,1);
		else 	if(q[id[i]].lei==2&&q[id[i]].k<=md) plu(q[id[i]].l,-1);
		else if(q[id[i]].lei==3)	tmp[i]=query(q[id[i]]);
	}
	for(int i=l;i<=r;i++)
	{
		if(q[id[i]].lei==1&&q[id[i]].k<=md) plu(q[id[i]].l,-1);
		else 	if(q[id[i]].lei==2&&q[id[i]].k<=md) plu(q[id[i]].l,1);
	}
	int cn=0;
	for(int i=l;i<=r;i++)
	{
		if(q[id[i]].lei==3)
		{
			if(tmp[i]>=q[id[i]].k)	cn++,mark[i]=1;
			else q[id[i]].k-=tmp[i],mark[i]=0;
			}	
		else 
		{
			if(q[id[i]].k<=md)
				cn++,mark[i]=1;
			else
				mark[i]=0;
		}
	}
	int l2=l+cn,l1=l;
	for(int i=l;i<=r;i++)
	{
		if(mark[i])	zhuan[l1++]=id[i];
		else zhuan[l2++]=id[i];
	}
	for(int i=l;i<=r;i++)
		id[i]=zhuan[i];
	solve(l,l1-1,L,md);
	solve(l1,r,md+1,R);
}
int ll,rr,vl;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),q[++cnt].lei=1,q[cnt].l=i,q[cnt].k=a[i];
	for(int i=1;i<=m;i++)
	{
		while(scanf("%c",&op)&&op!='C'&&op!='Q');
		if(op=='C')
		{
			int pos,vl;
			scanf("%d%d",&pos,&vl);
			q[++cnt].lei=2,q[cnt].l=pos,q[cnt].k=a[pos];
			q[++cnt].lei=1,q[cnt].l=pos,q[cnt].k=vl;
			a[pos]=vl;
		}
		else
			scanf("%d%d%d",&ll,&rr,&vl),q[++cnt].lei=3,q[cnt].l=ll,q[cnt].r=rr,q[cnt].k=vl;
	}
	for(int i=1;i<=cnt;i++)
		id[i]=i;
	solve(1,cnt,0,0x3f3f3f3f);
	for(int i=1;i<=cnt;i++)
		if(q[i].lei==3)
			printf("%d\n",ans[i]);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值