bzoj3110[Zjoi2013]K大数查询 整体二分+树状数组区间修改

Posted GXZlegend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj3110[Zjoi2013]K大数查询 整体二分+树状数组区间修改相关的知识,希望对你有一定的参考价值。

题目描述

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

输入

第一行N,M
接下来M行,每行形如1 a b c或2 a b c

输出

输出每个询问的结果

样例输入

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

样例输出

1
2
1


题解

整体二分+树状数组区间修改

当年naive的树套树题解

前两天由于要讲整体二分,所以自己YY出了这种带修改的整体二分写法:

由于每次插入的权值时固定的,因此可以把修改和询问放到一起整体二分。

设 $solve(b,e,l,r)$ 表示解决 $[b,e]$ 中的修改和询问,其中:修改的权值、询问的答案都在 $[l,r]$ 内。

那么如果 $l=r$ ,则区间内所有询问的答案都等于 $l$ 。

否则设 $mid=\\frac{l+r}2$ ,然后按照时间序扫一遍修改和询问。

对于修改:如果权值在 $[l,mid]$ 则丢到左区间,否则给这段区间整体+1并丢到右区间。

对于询问:如果区间和(实际含义为该次询问时区间内权值在 $[mid+1,r]$ 内的数的个数)大于等于询问的 $c$ ,说明 $[mid+1,r]$ 内有足够多的数,答案在 $[mid+1,r]$ 内,丢到右区间;否则把 $c$ 减去这个区间和,并丢到左区间。

清空数据结构,递归左右区间。注意对于左右区间也需要维护时间序。

需要实现:区间修改、区间求和,可以使用树状数组区间修改实现。具体可以参考 【bzoj3132】上帝造题的七分钟

时间复杂度 $O(n\\log^2n)$ ,比树套树快了10倍多。。。(当然也有可能是我当年写的不好吧)

注意本题爆int,因此需要unsigned int(比long long略快)。

#include <cstdio>
#define N 50010
typedef unsigned int uint;
struct data
{
	int opt , a , b , c , id;
}q[N] , t[N];
int flag[N] , ans[N] , n;
struct bit
{
	uint v[N];
	inline void add(int x , int a)
	{
		int i;
		for(i = x ; i <= n ; i += i & -i) v[i] += a;
	}
	inline uint query(int x)
	{
		int i;
		uint ans = 0;
		for(i = x ; i ; i -= i & -i) ans += v[i];
		return ans;
	}
}A , B;
inline void modify(int x , int a)
{
	A.add(x , a) , B.add(x , a * x);
}
inline uint ask(int x)
{
	return (x + 1) * A.query(x) - B.query(x);
}
void solve(int b , int e , int l , int r)
{
	if(l == r)
	{
		int i;
		for(i = b ; i <= e ; i ++ )
			if(q[i].opt == 2)
				ans[q[i].id] = l;
		return;
	}
	int mid = (l + r) >> 1 , i , lp = b - 1 , rp = e + 1;
	uint v;
	for(i = b ; i <= e ; i ++ )
	{
		if(q[i].opt == 1)
		{
			if(q[i].c > mid) modify(q[i].a , 1) , modify(q[i].b + 1 , -1) , t[--rp] = q[i];
			else t[++lp] = q[i];
		}
		else
		{
			v = ask(q[i].b) - ask(q[i].a - 1);
			if((uint)q[i].c <= v) t[--rp] = q[i];
			else q[i].c -= v , t[++lp] = q[i];
		}
	}
	for(i = b ; i <= e ; i ++ )
		if(q[i].opt == 1 && q[i].c > mid)
			modify(q[i].a , -1) , modify(q[i].b + 1 , 1);
	for(i = b ; i <= lp ; i ++ ) q[i] = t[i];
	for(i = rp ; i <= e ; i ++ ) q[i] = t[e + rp - i];
	solve(b , lp , l , mid) , solve(rp , e , mid + 1 , r);
}
int main()
{
	int m , i;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= m ; i ++ )
	{
		scanf("%d%d%d%d" , &q[i].opt , &q[i].a , &q[i].b , &q[i].c) , q[i].id = i;
		if(q[i].opt == 2) flag[i] = 1;
	}
	solve(1 , m , -n , n);
	for(i = 1 ; i <= m ; i ++ )
		if(flag[i])
			printf("%d\\n" , ans[i]);
	return 0;
}

 

 

以上是关于bzoj3110[Zjoi2013]K大数查询 整体二分+树状数组区间修改的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ3110[Zjoi2013]K大数查询 树套树

bzoj3110: [Zjoi2013]K大数查询

bzoj3110: [Zjoi2013]K大数查询

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

Bzoj3110: [Zjoi2013]K大数查询

BZOJ 3110 [Zjoi2013]K大数查询