BZOJ3110 [Zjoi2013]K大数查询 树套树 线段树 整体二分 树状数组

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - BZOJ3110


题意概括

  有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c。如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint


 UPD(2018-04-01):之前抄的树套树是真的丑到爆。今天用分治做了一遍QAQ。

题解

  让我们来考虑神奇的分治算法。

  整体二分!!(当你会了)

  首先当你已经掌握了树状数组的区间加和区间询问(如果不会->点这里

  我们考虑二分答案。

  注意进行以下操作要严格按照输入时间先后顺序来。

  首先对于加进去的数字c,我们把他变成n-c+1,这样就把询问前k大变成了前k小。

  如果是修改操作,如果修改的值比当前的mid值小,就修改,并扔到左区间里面。否则扔到右边。

  如果是询问操作,如果在当前的状态下,该询问的区间内查询到的数的个数res比当前询问的c要大(或者相等),那么显然答案在左区间,把他扔到左边,否则把他的c减掉res再扔到右边去。

  然后递归分治两个区间就可以了。

  (本质是个二分答案的升级版)

  

  然而博主非常非常非常非常非常非常的菜。千辛万苦调出样例,交一发WA。找了半天发现树状数组打萎掉了。

  然后推式子不下于3遍。校对lych大佬的代码不下于5遍,还是没发现错误。

  woc心态爆炸bonebonebone!

  还是没发现错误。

  

  

   

 

  发现了。最难发现的地方。tree[2][N]打成了tree[N][2]……QAQ

  

 

  

 

  

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=50005;
int n,m,id[N],tmpL[N],tmpR[N];
LL tree[2][N];
int lowbit(int x){
	return x&-x;
}
void add(int t,int x,int y){
	for (;x<=n+1;x+=lowbit(x))
		tree[t][x]+=y;
}
void update(int L,int R,int v){
	add(0,L,v),add(1,L,v*L);
	add(0,R+1,-v),add(1,R+1,-v*(R+1));
}
LL sum(int t,int x){
	LL ans=0;
	for (;x>0;x-=lowbit(x))
		ans+=tree[t][x];
	return ans;
}
LL query(int L,int R){
	return sum(0,R)*(R+1)-sum(0,L)*L-sum(1,R)+sum(1,L);
}
struct opts{
	int type,a,b,c,ans;
	void get(){
		scanf("%d%d%d%d",&type,&a,&b,&c);
		if (type==1)
			c=n-c+1;
	}
}a[N];
void solve(int xL,int xR,int L,int R){
	if (L>R)
		return;
	if (xL==xR){
		for (int i=L;i<=R;i++)
			a[id[i]].ans=xL;
		return;
	}
	int xmid=(xL+xR)>>1;
	int l=0,r=0;
	for (int i=L;i<=R;i++)
		if (a[id[i]].type==1){
			if (a[id[i]].c<=xmid)
				tmpL[++l]=id[i],update(a[id[i]].a,a[id[i]].b,1);
			else
				tmpR[++r]=id[i];
		}
		else {
			LL res=query(a[id[i]].a,a[id[i]].b);
			if (res>=a[id[i]].c)
				tmpL[++l]=id[i];
			else
				tmpR[++r]=id[i],a[id[i]].c-=res;
		}
	for (int i=1;i<=l;i++)
		if (a[tmpL[i]].type==1)
			update(a[tmpL[i]].a,a[tmpL[i]].b,-1);
	for (int i=L;i<=L+l-1;i++)
		id[i]=tmpL[i-(L-1)];
	for (int i=R-r+1;i<=R;i++)
		id[i]=tmpR[i-(R-r)];
	solve(xL,xmid,L,L+l-1);
	solve(xmid+1,xR,R-r+1,R);
}
int main(){
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
		a[i].get(),id[i]=i;
	memset(tree,0,sizeof tree);
	solve(1,2*n+1,1,m);
	for (int i=1;i<=m;i++)
		if (a[i].type==2)
			printf("%d\n",n-a[i].ans+1);
	return 0;
}

  

 

 

———————old———————(2017-12-19)

题解

  树套树裸题。

  外层套权值线段树,内层套区间线段树。

  标记永久化比较好写。

  空间随便卡卡就过去了。


 

代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
typedef long long LL;
const int N=50005,NN=N*2,K=220;
struct Position_Segment_Tree{
	int tot,ls[N*K],rs[N*K];
	LL add[N*K],sum[N*K];
	void clear(){
		tot=0;
		memset(ls,0,sizeof ls);
		memset(rs,0,sizeof rs);
		memset(add,0,sizeof add);
		memset(sum,0,sizeof sum);
	}
	void update(int &rt,int le,int ri,int xle,int xri){
		if (!rt)
			rt=++tot;
		if (xle<=le&&ri<=xri){
			add[rt]++;
			return;
		}
		sum[rt]+=xri-xle+1;
		int mid=(le+ri)>>1;
		if (xri<=mid)
			update(ls[rt],le,mid,xle,xri);
		else if (xle>mid)
			update(rs[rt],mid+1,ri,xle,xri);
		else {
			update(ls[rt],le,mid,xle,mid);
			update(rs[rt],mid+1,ri,mid+1,xri);
		}
	}
	LL query(int rt,int le,int ri,int xle,int xri){
		if (!rt)
			return 0;
		if (xle<=le&&ri<=xri)
			return sum[rt]+add[rt]*(xri-xle+1);
		int mid=(le+ri)>>1;
		LL res=add[rt]*(xri-xle+1);
		if (xri<=mid)
			return res+query(ls[rt],le,mid,xle,xri);
		else if (xle>mid)
			return res+query(rs[rt],mid+1,ri,xle,xri);
		else
			return res+query(ls[rt],le,mid,xle,mid)
					  +query(rs[rt],mid+1,ri,mid+1,xri);
	}
}PST;
int n,nn,m;
int tr[NN*4];
void update(int rt,int le,int ri,int pos,int xle,int xri){
	PST.update(tr[rt],1,n,xle,xri);
	if (le==ri)
		return;
	int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
	if (pos<=mid)
		update(ls,le,mid,pos,xle,xri);
	else
		update(rs,mid+1,ri,pos,xle,xri);
}
int query(int rt,int le,int ri,int xle,int xri,LL k){
	if (le==ri)
		return le;
	int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
	LL Rz=PST.query(tr[rs],1,n,xle,xri);
	if (k<=Rz)
		return query(rs,mid+1,ri,xle,xri,k);
	else
		return query(ls,le,mid,xle,xri,k-Rz);
}
int main(){
	scanf("%d%d",&n,&m);
	nn=n*2+1;
	PST.clear();
	while (m--){
		int op,a,b,c;
		scanf("%d%d%d%d",&op,&a,&b,&c);
		if (op==1)
			update(1,1,nn,c+n+1,a,b);
		else
			printf("%d\n",query(1,1,nn,a,b,c)-n-1);
	}
	return 0;
}

  

转载于:https://www.cnblogs.com/zhouzhendong/p/BZOJ3110.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值