[BZOJ 3110] [Zjoi2013] K大数查询 【树套树】

题目链接: BZOJ - 3110

题目分析

这道题是一道树套树的典型题目,我们使用线段树套线段树,一层是区间线段树,一层是权值线段树。一般的思路是外层用区间线段树,内层用权值线段树,但是这样貌似会很难写。多数题解都使用了外层权值线段树,内层区间线段树,于是我就这样写了。每次插入会在 logn 棵线段树中一共建 log^2(n) 个结点,所以空间应该开到 O(nlog^2(n)) 。由于这道题查询的是区间第 k 大,所以我们存在线段树中的数值是输入数值的相反数(再加上 n 使其为正数),这样查第 k 小就可以了。在查询区间第 k 大值的时候,我们用类似二分的方法,一层一层地逼近答案。

写代码的时候出现的错误:在每一棵区间线段树中修改数值的时候,应该调用的是像 Insert(Lc[x], 1, n, l, r) 这样子,但我经常写成 Insert(x << 1, s, t, l, r) 之类的。注意!

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>

using namespace std;

const int MaxN = 100000 + 5, MaxM = 100000 * 16 * 16 + 5;

int n, m, f, a, b, c, Index, Ans;
int Root[MaxN * 4], Lc[MaxM], Rc[MaxM], Sum[MaxM], Lazy[MaxM];

inline int gmin(int a, int b) {
	return a < b ? a : b;
}
inline int gmax(int a, int b) {
	return a > b ? a : b;
}

int Get(int x, int s, int t, int l, int r) {
	if (l <= s && r >= t) return Sum[x];
	int p = 0, q = 0, m = (s + t) >> 1;
	if (l <= m) p = Get(Lc[x], s, m, l, r);
	if (r >= m + 1) q = Get(Rc[x], m + 1, t, l, r);
	return (p + q + Lazy[x] * (gmin(t, r) - gmax(s, l) + 1));
}

int GetKth(int l, int r, int k) {
	int s = 1, t = n * 2, m, x = 1, Temp;
	while (s != t) {
		m = (s + t) >> 1;
		if ((Temp = Get(Root[x << 1], 1, n, l, r)) >= k) {
			t = m; x = x << 1;
		}
		else {
			s = m + 1; x = x << 1 | 1; k -= Temp;
		}
	}
	return s;
}

void Insert(int &x, int s, int t, int l, int r) {
	if (x == 0) x = ++Index;
	if (l <= s && r >= t) {
		Sum[x] += t - s + 1;
		++Lazy[x];
		return;
	}
	int m = (s + t) >> 1;
	if (l <= m) Insert(Lc[x], s, m, l, r);
	if (r >= m + 1) Insert(Rc[x], m + 1, t, l, r);
	Sum[x] = Sum[Lc[x]] + Sum[Rc[x]] + Lazy[x] * (t - s + 1);
}

void Add(int l, int r, int Num) {
	int s = 1, t = n * 2, m, x = 1;
	while (s != t) {
		Insert(Root[x], 1, n, l, r);
		m = (s + t) >> 1;
		if (Num <= m) {
			t = m;
			x = x << 1;
		}
		else {
			s = m + 1;
			x = x << 1 | 1;
		}
	}
	Insert(Root[x], 1, n, l, r);
}

int main()
{
	scanf("%d%d", &n, &m);
	Index = 0;
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d%d%d", &f, &a, &b, &c);
		if (f == 1) {
			c = -c + n + 1;
			Add(a, b, c);
		}
		else {
			Ans = GetKth(a, b, c);
			Ans = -Ans + n + 1;
			printf("%d\n", Ans);
		}
	}
	return 0;
}

  

时间: 2024-10-11 12:40:51

[BZOJ 3110] [Zjoi2013] K大数查询 【树套树】的相关文章

树套树专题——bzoj 3110: [Zjoi2013] K大数查询 &amp; 3236 [Ahoi2013] 作业 题解

[原题1] 3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MB Submit: 978  Solved: 476 Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M 接下来M行,每行形如1 a b c或2 a b c Outpu

BZOJ 3110: [Zjoi2013]K大数查询 [树套树]

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6050  Solved: 2007[Submit][Status][Discuss] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a

BZOJ 3110: [Zjoi2013]K大数查询( 树状数组套主席树 )

BIT+(可持久化)权值线段树, 用到了BIT的差分技巧. 时间复杂度O(Nlog^2(N)) ----------------------------------------------------------------------------------------- #include<cstdio> #include<cctype> #include<cstring> #include<algorithm> using namespace std;

BZOJ 3110: [Zjoi2013]K大数查询 [整体二分]

有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. N,M<=50000,N,M<=50000a<=b<=N1操作中abs(c)<=N2操作中c<=Maxlongint 之前用树套树抄过一次...然而我并不适合写那玩意儿... 加上时间序的整体二分 普通的整体二分先处理了所有$[l,mid]$的影响因子在计算询问的答案来分组

[BZOJ 3110][Zjoi2013]K大数查询(整体二分+BIT)

Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Solution 标解似乎是树套树?=w= 二分答案. 对于每一个修改,如果c>mid就进行操作,并划到后一个集合里,反之加入前一个集合:对于每一个询问,如果a-b中的数大于c个就划到后一个集合里,反之要减掉a-b中数的个数并加入前一个集合.然后对于两个集合递归下去blahb

bzoj:3110: [Zjoi2013]K大数查询

Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a b c或2 a b c Output 输出每个询问的结果 Sample Input 2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3 Sample Output 1 2 1 树套树……一直

bzoj 3110 [Zjoi2013]K大数查询(树套树)

Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a b c或2 a b c Output 输出每个询问的结果 Sample Input 2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3 Sample Output 1 2 1 HINT [样

BZOJ 3110 [Zjoi2013]K大数查询 ——整体二分

[题目分析] 整体二分显而易见. 自己YY了一下用树状数组区间修改,区间查询的操作. 又因为一个字母调了一下午. 貌似树状数组并不需要清空,可以用一个指针来维护,可以少一个log 懒得写了. [代码] #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 50005 #define inf

3110: [Zjoi2013]K大数查询 树状数组套线段树

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1384  Solved: 629[Submit][Status] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a b c或2 a b