【BZOJ】3110 [Zjoi2013]K大数查询 整体二分+树状数组 || 树套树

题目传送门

让我缓缓……写了整整三天,终于A掉了……这题,真的恶心啊……

这题最大的问题就是细节的问题,在码代码的时候记得一定要保持自己的状态,不能有一点的松懈,否则就像我一样……

WA了10+发,TLE了三四发,心累啊……

这题还是整体二分,对于那些写树套树的大佬我只能表示%%%了。

因为题目给出的条件是abs(c)<=n,那么可能存在负数,我们可以用n-c来替换c,那么c的值域就变成了[0,2*n],问题就变成了求区间内第k小的数了。

我们把修改和询问一起整体二分,取操作区间的一半,对于左半边的修改操作,我们把他们都加到树状数组中,并分到左边;否则就分到右边。

然后对于接下来的询问,若当前区间内数字个数超过询问需求量,那么就把这个询问分到左边,否则就修改询问需求量,把它分到右边。

接着清零树状数组,递归求解即可。


彩蛋:

1.再一次orzZH大佬,当我对着我那鬼畜WA掉的代码冥思苦想时,ZH大佬从旁边淡淡的来了一句:“还没AC?看看我的代码吧。”

QAQ……大佬不愧是大佬啊,太厉害啦,%%%%%%

2.原来CSDN一天最多交五篇博客,我说为什么我交了五篇以后就一直显示“保存失败,请稍后重试”……


代码写的较丑,不喜勿喷QWQ

附上AC代码:

#include <cstdio>
#include <cctype>
#define N 60010
using namespace std;

struct note{
	long long o,x,y,w,wz;
}a[N],q1[N],q2[N];
long long n,m,size,ans[N],t1[N],t2[N];

inline char nc(){
	static char ch[100010],*p1=ch,*p2=ch;
	return p1==p2&&(p2=(p1=ch)+fread(ch,1,100010,stdin),p1==p2)?EOF:*p1++;
}

inline void read(long long &a){
	static char c=nc();int f=1;
	for (;!isdigit(c);c=nc()) if (c=='-') f=-1;
	for (a=0;isdigit(c);a=a*10+c-'0',c=nc());
	a*=f;return;
}

inline void updata(int x,long long w){
	long long ww=(long long)w*x;
	for (int i=x; i<=n; i+=i&-i) t1[i]+=w,t2[i]+=ww;
	return;
}

inline long long query(int x){
	long long sum=0;
	for (int i=x; i; i-=i&-i) sum+=t1[i];
	sum*=(long long)x+1;
	for (int i=x; i; i-=i&-i) sum-=t2[i];
	return sum;
}

inline void so(int l,int r,int ql,int qr){
	if (l>r||ql>qr) return;
	if (ql==qr){
		for (int i=l; i<=r; ++i)
			if (a[i].o==2) ans[a[i].wz]=ql;
		return;
	}
	int mid=(ql+qr)>>1,ll=0,rr=0;
	for (int i=l; i<=r; ++i)
		if (a[i].o==1){
			if (a[i].w<=mid) q1[++ll]=a[i],updata(a[i].x,1),updata(a[i].y+1,-1);
				else q2[++rr]=a[i];
		}
		else{
			long long tmp=query(a[i].y)-query(a[i].x-1);
			if (a[i].w<=tmp) q1[++ll]=a[i];
				else a[i].w-=tmp,q2[++rr]=a[i];
		}
	for (int i=1; i<=ll; ++i) if (q1[i].o==1) updata(q1[i].x,-1),updata(q1[i].y+1,1);
	for (int i=1; i<=ll; ++i) a[l+i-1]=q1[i];
	for (int i=1; i<=rr; ++i) a[l+ll+i-1]=q2[i];
	return so(l,l+ll-1,ql,mid),so(l+ll,r,mid+1,qr);
}

int main(void){
	read(n),read(m);
	for (int i=1; i<=m; ++i){
		read(a[i].o),read(a[i].x),read(a[i].y),read(a[i].w);
		if (a[i].o==1) a[i].w=n-a[i].w;
			else a[i].wz=++size;
	}
	so(1,m,0,n<<1);
	for (int i=1; i<=size; ++i) printf("%lld\n",n-ans[i]);
	return 0;
}


10.1更新

最近学了一波线段树套线段树(因为还是看不懂线段树套平衡树),回想起当时做这题的时候网上的题解都是树套树,就决定回来填一下坑。

刷题心得:

  1. 做题思路要清晰,不要在自己很累的时候做题。(特别是睡觉以前)
  2. 突然发现自己成为了以前自己仰望的那些大佬中的一个,终于初窥线段树套线段树的门径了(详情见上文)
对于这题我们可以建立权值线段树套区间线段树。

对于修改操作,相当于单点修改权值线段树,区间修改区间线段树。即在每一个包含当前权值w的区间[l,r]对应的内层线段树上在给出的[L,R]上加一。

对于询问操作,相当于单点询问权值线段树,区间询问区间线段树。即对于当前区间[l,r]的右儿子询问区间和t,若t小于当前的排名,则往左儿子递归;否则更新排名,然后往右儿子递归。

因为这题的n<=50000,所以我们要动态开点。

树套树对于这题来说就是一个暴力的做法,不管是时间还是空间都被上面的整体二分艹爆了。

(实测——整体二分:7964kb,1784ms;树套树:470404kb,8780ms)(差距真的好大啊……)

p.s.不知为何,百度上排在搜索第一页的各位大佬的代码在洛谷和BZOJ上都WA掉了……只有CODE[VS]比较友好,都AC了……

附上AC代码:

#include <cstdio>
#define lt (k<<1)
#define rt (k<<1|1)
#define mid ((l+r)>>1)
using namespace std;

const int N=50005;
typedef long long ll;
ll sum[N*400],lz[N*400],w;
int n,m,x,y,o,size,ch[N*400][2],root[N<<2];

inline void change(int &k,int l,int r,int ql,int qr){
	if (!k) k=++size;
	sum[k]+=(ll)qr-ql+1;
	if (l>=ql&&r<=qr) return (void)(++lz[k]);
	if (qr<=mid) change(ch[k][0],l,mid,ql,qr);
	else if (ql>mid) change(ch[k][1],mid+1,r,ql,qr);
	else change(ch[k][0],l,mid,ql,mid),change(ch[k][1],mid+1,r,mid+1,qr);
}

inline void updata(int k,int l,int r,int ql,int qr,int w){
	change(root[k],1,n,ql,qr);
	if (l==r) return;
	if (w<=mid) updata(lt,l,mid,ql,qr,w);
	else updata(rt,mid+1,r,ql,qr,w);
}

inline ll ask(int k,int l,int r,int ql,int qr){
	if (!k) return 0;
	if (l>=ql&&r<=qr) return sum[k];
	ll ret=1ll*lz[k]*(qr-ql+1);
	if (qr<=mid) ret+=ask(ch[k][0],l,mid,ql,qr);
	else if (ql>mid) ret+=ask(ch[k][1],mid+1,r,ql,qr);
	else ret+=ask(ch[k][0],l,mid,ql,mid)+ask(ch[k][1],mid+1,r,mid+1,qr);
	return ret;
}

inline int query(int k,int l,int r,int ql,int qr,ll w){
	if (l==r) return l;
	ll t=ask(root[rt],1,n,ql,qr);
	if (t>=w) return query(rt,mid+1,r,ql,qr,w);
	else return query(lt,l,mid,ql,qr,w-t);
}

int main(void){
	scanf("%d%d",&n,&m);
	while (m--){
		scanf("%d%d%d%lld",&o,&x,&y,&w);
		if (o==1) updata(1,1,n,x,y,w);
		else printf("%d\n",query(1,1,n,x,y,w));
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值