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
题解
整体二分+树状数组区间修改
前两天由于要讲整体二分,所以自己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大数查询 整体二分+树状数组区间修改的主要内容,如果未能解决你的问题,请参考以下文章