BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)

题目大意:有一些位置,这些位置上可以放若干个数字。现在有两种操作。

1.在区间l到r上添加一个数字x

2.求出l到r上的第k大的数字是什么

思路:这种题一看就是树套树,关键是怎么套,怎么写。(话说我也不会来着。。)最容易想到的方法就是区间线段树套一个权值线段树,但是区间线段树上的标记就会变得异常复杂。所以我们就反过来套,用权值线段树套区间线段树。这样修改操作在外线段树上就变成了单点修改,外线段树就不用维护标记了。在里面的区间线段树上维护标记就容易多了。具体实现见代码。

CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 50010
#define CNT (r - l + 1)
using namespace std;

int total,asks;

struct ValSegTree{
	ValSegTree *son[2];
	int cnt,c;

	ValSegTree() {
		son[0] = son[1] = NULL;
		cnt = c = 0;
	}
	void PushDown(int k) {
		if(son[0] == NULL)	son[0] = new ValSegTree();
		if(son[1] == NULL)	son[1] = new ValSegTree();
		if(c) {
			son[0]->cnt += c * (k - (k >> 1));
			son[0]->c += c;
			son[1]->cnt += c * (k >> 1);
			son[1]->c += c;
			c = 0;
		}
	}
	void Modify(int l,int r,int x,int y) {
		if(l == x && r == y) {
			cnt += CNT;
			c++;
			return ;
		}
		PushDown(CNT);
		int mid = (l + r) >> 1;
		if(y <= mid)	son[0]->Modify(l,mid,x,y);
		else if(x > mid)	son[1]->Modify(mid + 1,r,x,y);
		else {
			son[0]->Modify(l,mid,x,mid);
			son[1]->Modify(mid + 1,r,mid + 1,y);
		}
		cnt = son[0]->cnt + son[1]->cnt;
	}
	int Ask(int l,int r,int x,int y) {
		if(!cnt)	return 0;
		if(l == x && r == y)	return cnt;
		PushDown(CNT);
		int mid = (l + r) >> 1;
		if(y <= mid)	return son[0]->Ask(l,mid,x,y);
		if(x > mid)		return son[1]->Ask(mid + 1,r,x,y);
		int left = son[0]->Ask(l,mid,x,mid);
		int right = son[1]->Ask(mid + 1,r,mid + 1,y);
		return left + right;
	}
};
struct IntSegTree{
	IntSegTree *son[2];
	ValSegTree *root;

	IntSegTree() {
		son[0] = son[1] = NULL;
		root = new ValSegTree();
	}
	void Modify(int l,int r,int _l,int _r,int x) {
		root->Modify(1,total,_l,_r);
		if(l == r)	return ;
		int mid = (l + r) >> 1;
		if(son[0] == NULL)	son[0] = new IntSegTree();
		if(son[1] == NULL)	son[1] = new IntSegTree();
		if(x <= mid)	son[0]->Modify(l,mid,_l,_r,x);
		else	son[1]->Modify(mid + 1,r,_l,_r,x);
	}
	int Ask(int l,int r,int _l,int _r,int k) {
		if(l == r)	return l;
		int mid = (l + r) >> 1;
		if(son[0] == NULL)	son[0] = new IntSegTree();
		if(son[1] == NULL)	son[1] = new IntSegTree();
		int temp = son[1]->root->Ask(1,total,_l,_r);
		if(k > temp)	return son[0]->Ask(l,mid,_l,_r,k - temp);
		else	return son[1]->Ask(mid + 1,r,_l,_r,k);
	}
}root;

int main()
{
	cin >> total >> asks;
	for(int flag,x,y,z,i = 1;i <= asks; ++i) {
		scanf("%d%d%d%d",&flag,&x,&y,&z);
		if(flag == 1)	root.Modify(1,total,x,y,z);
		else	printf("%d\n",root.Ask(1,total,x,y,z));
	}
	return 0;
}
时间: 2024-10-14 19:05:25

BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)的相关文章

[bzoj 3110][zjoi 2013]K大数查询

传送门 Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Solution 这题是可以区间线段树套权值线段树来做的 但是我想练一下整体二分 顺便写一个树状数组的区间修改+区间查询 class BIT { #define NM 50005 #define lb(x) (x&(-x)) private: ll t1[NM],t2

数据结构(树套树):ZJOI 2013 K大数查询

有几个点卡常数…… 发现若第一维为位置,第二维为大小,那么修改时第一维修改区间,查询时第一维查询区间,必须挂标记.而这种情况下标记很抽象,而且Push_down不是O(1)的,并不可行. 那要怎么做呢?不妨交换一下,第一维为大小,第二维为位置,在第二维中挂标记,这样Push_down就是O(1)的了. 做完这道题,我最大的启发就是:树套树不适于在第一维挂标记,因为标记的维度会是一维的,根本不好维护. 1 #include <iostream> 2 #include <cstring>

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

http://www.lydsy.com/JudgeOnline/problem.php?id=3110 题意:-- 思路:其实和之前POJ那道题差不多,只不过是换成区间更新,而且是第k大不是第k小,第k大是降序的第k个,在二分询问的时候需要注意和第k小的不同细节. 树状数组比线段树快了几倍,所以说树状数组区间更新区间查询是一个值得学的姿势啊. 线段树: 1 //9028 kb 7484 ms 2 #include <cstdio> 3 #include <cstring> 4 #

[ZJOI 2013] K大数查询

[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=3110 [算法] 整体二分 + 线段树 时间复杂度 : O(NlogN ^ 2) [代码] #include<bits/stdc++.h> using namespace std; #define MAXN 500010 typedef long long ll; typedef long double ld; struct query { int type , a , b; ll

树套树专题——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大数查询( 树状数组套主席树 )

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

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大数查询 【树套树】

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

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