初学整体二分 P2617 Dynamic Rankings

整体二分是二分答案的一种:
不同于普通的二分,整体二分正如其名“整体”二分。普通的二分一般用与解决一次询问问题,“整体二分”可以对多个询问同时进行二分。
其大致思想为对原来的询问对时间戳排序(保证正确性),先对总的询问进行分割,对当前的假设的答案将询问分为两部分,左边为符合条件,右边反之(具体看情况)。就好比:有一群人要买东西,你去问他们“ans”这个价格你们会买吗,肯定有一定的人数会买,另一半的人不会买。对于会买的那群人你又会去问比之前更高的价格…对于不会买的那群人你又会去问比之前更低的价格…,一直执行下去就能到他们每个人的心里价位。
大致流程:

void fun(nowquery,l,r)
{
	ans=l+r>>1;
	for(i:n)
	{
		if(check(nowquery[i],ans)) push to leftquery
		else push to rightquery
	}
	fun(leftquery,l,mid);
	fun(rightquery,mid+1,r);
}

以 P2617 Dynamic Rankings 为例。

单点修改,区间第k小,n,m<=100000
区间第k小可以二分答案。对单个询问 对当前答案统计比现在小的的个数num,如果数量小于等于这num 则左边,反之右边。
首先我们建立时间日志表(两种类型1为修改,2为询问),我们可以将数值的赋值看为修改当前数,对于询问,在之前的时间下增加时间。

对于一个假定ans来讲,我们现在要做的就是对与一个询问求出他的区间中比ans小的有多少个。
按照之前的时间来一步一步的进行操作。对于一个修改操作来讲,如果其值大于ans显然对当前整个询问无影响,将其放到 rightquery,其值<=ans以其原来的下标加入树状数组中。询问操作直接在树状数组中查找在当前区间的个数 如果 >= 询问的k(k为第k小)值 Push leftquery(>=ans 说明ans足够大需要减小),反之Push rightquery,(这里的左右询问区间一定要按时间排序)递归 (leftquery,l ,ans) (right,ans+1,r)。对于一个修改操作我们发现,他原来在树状数组中表示有或者无,那么我们在修改操作是可以考虑先将原来的抵消掉,在重新赋值。即可实现修改。

#include <bits/stdc++.h>
#include <stdio.h>
#include <iostream>
using namespace std;
const int maxn=1e5+100l;
struct Query{
	int op,l,r,k,id;
}qu[maxn<<2],q1[maxn<<2],q2[maxn<<2];

struct Tree{
	int sum[maxn];
	void add(int p,int x){ for(;p<maxn;p += p&-p) sum[p]+=x;}
	int getsum(int p) {int ret=0;for(;p>0;p-=p&-p) ret += sum[p];return ret;}
}T;

int ans[maxn];
void slove(int l,int r,int ql,int qr)
{
	if(ql>qr) return;
	if(l==r)  {
		for(int i=ql;i<=qr;++i)if(qu[i].op==2) ans[qu[i].id]=l;
		return;
	}
	int mid=l+r>>1;
	int l1=0,l2=0;
	for(int i=ql;i<=qr;++i) {
		if(qu[i].op==1) {
			if(qu[i].l<=mid) T.add(qu[i].id,qu[i].r), q1[l1++]=qu[i];
			else q2[l2++]=qu[i];
		} else {
			int k=T.getsum(qu[i].r)-T.getsum(qu[i].l-1);
			if(qu[i].k<=k) q1[l1++]=qu[i];
			else
			{
				qu[i].k-=k;
				q2[l2++]=qu[i];
			}
		}
	}
	for(int i=0;i<l1;++i) if(q1[i].op==1) T.add(q1[i].id,-q1[i].r);
	for(int i=0;i<l1;++i) qu[i+ql]=q1[i];
	for(int i=0;i<l2;++i) qu[i+l1+ql]=q2[i];
	slove(l,mid,ql,ql+l1-1);
	slove(mid+1,r,ql+l1,qr);
}
int a[maxn];


int main()
{
	int t;
	int n,m;
	while(~scanf("%d%d",&n,&m))
	{
		int idx=0;
		for(int i=1;i<=n;++i) {
			int x;
			scanf("%d",&x);a[i]=x;
			qu[++idx]={1,x,1,0,i};
		}
		for(int i=1;i<=m;++i) {
			char op[2];
			scanf("%s",op);
			int l,r,k;
			if(op[0]=='Q')
			{
				scanf("%d%d%d",&l,&r,&k);
				qu[++idx]={2,l,r,k,i};
			}
			else
			{
				scanf("%d%d",&l,&r);
				qu[++idx]={1,a[l],-1,0,l};
				a[l]=r;
				qu[++idx]={1,a[l],1,0,l};
			}
		}
		memset(ans,-1,sizeof ans);
		slove(-1e9,1e9,1,idx);
		for(int i=1;i<=m;++i)
		{
			if(ans[i]!=-1)printf("%d\n",ans[i]);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值