BZOJ-3110-K大数查询-ZJOI2013-整体二分

描述

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

如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。


分析

  • 今天终于看懂了一个整体二分的题目. 发现这真是一种很BT的做法.
  • 二分答案区间(L, R), 判断中间值M = L+(R-L)/2. 每次清空线段树(直接在根节点上打删除标记, 不要用memset直接清, 会T..).  线段树维护的是在区间(a, b)中比二分的答案M大的数有多少. 处理在(l, r)中的操作, 如果是插入(a, b, c), 判断c和M的大小, c大于M的话在线段树(a, b)区间打增加标记表示+1, 同时操作类型标记为1, c不大于M操作类型标记为0; 如果是查询(a, b, c)操作, 当在线段树(a, b)区间和大于c则说明比M大的超过k个, 那么答案一定比M大, 操作类型标记为1, 否则标记为0.
  • 将所有标记为0的点去下一层(L, M)继续 (可以直接按类型sort, 也可以借鉴归并排序的思想O(n)的归并), 标记为1的点去(M+1, R)继续二分.
  • 当答案区间(L = R)时, 操作区间所有的查询操作的答案都为L.
  • 另 : 1. 在线段树操作时传入太多参数会严重拖慢效率.
  •       2. 在结构体里定义的宏在外面一样用.

代码

#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
using namespace std;

const int maxn = 50000 + 10;

#define M  (L+((R-L)>>1))

struct SegmentTree {
	int y1, y2;
	int sumv[maxn<<2], addv[maxn<<2];
	bool clr[maxn<<2];
	
	void init() {
		sumv[1] = addv[1] = 0;
		clr[1] = 1;
	}
		
	#define lc (o<<1)
	#define rc (o<<1^1)
	
	void update(int o) {
		sumv[o] = sumv[lc] + sumv[rc];
	}
	
	void pushdown(int o, int L, int R) {
		if(clr[o]) {
			sumv[lc] = sumv[rc] = addv[lc] = addv[rc] = 0;
			clr[lc] = clr[rc] = 1;
			clr[o] = 0;
		}
		if(addv[o]) {
			int& x = addv[o];
			addv[lc] += x;
			addv[rc] += x;
			sumv[lc] += (M-L+1) * x;
			sumv[rc] += (R-M) * x;
			x = 0;
		}
	}
	
	void modify(int o, int L, int R) {
		if(y1 <= L && R <= y2) {
			addv[o]++;
			sumv[o] += (R-L+1);
		} else {
			pushdown(o, L, R);
			if(y1 <= M) modify(lc, L, M);
			if(y2 > M) modify(rc, M+1, R);
			update(o);
		}
	}
	
	int query(int o, int L, int R) {
		if(y1 <= L && R <= y2) return sumv[o];
		pushdown(o, L, R);
		int ret = 0;
		if(y1 <= M) ret += query(lc, L, M);
		if(y2 > M) ret += query(rc, M+1, R);
		return ret;
	}
	
} seg;

struct Node {
	int opt, id, l, r, v, k;
	bool operator < (const Node& rhs) const {
		if(k != rhs.k) return k < rhs.k;
		return id < rhs.id;
	}
} qst[maxn];

int n, m;
int ans[maxn];

void solve(int L, int R, int l, int r) {
	if(l > r) return;
	if(L == R) {
		for(int i = l; i <= r; i++) {
			Node& x = qst[i];
			if(x.opt == 2) ans[x.id] = L;
		}
		return;
	}
	seg.init();
	int t = l-1;
	for(int i = l; i <= r; i++) {
		Node& x = qst[i];
		if(x.opt == 1) {
			if(x.v > M) {
				seg.y1 = x.l;
				seg.y2 = x.r;
				seg.modify(1, 1, n);
				x.k = 1;
			} else {
				x.k = 0; t++;
			}
		} else {
			seg.y1 = x.l;
			seg.y2 = x.r;
			int s = seg.query(1, 1, n);
			if(x.v <= s) x.k = 1;
			else x.k = 0, t++, x.v -= s;
		}
	}
	sort(qst + l, qst + r + 1);
	solve(L, M, l, t);
	solve(M+1, R, t+1, r);
}

int main()
{
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= m; i++) {
		int opt, l, r, v;
		scanf("%d %d %d %d", &opt, &l, &r, &v);
		qst[i] = (Node) {opt, i, l, r, v};
	}
	memset(ans, -1, sizeof(ans));
	solve(0, n, 1, m);
	for(int i = 1; i <= m; i++)
		if(ans[i] != -1) printf("%d\n", ans[i]);
	return 0;
}

      
      
     
     
    
    

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值