【BZOJ3110】【codevs1616】K大数查询,权值线段树套普通线段树

Time:2016.05.09

Author:xiaoyimi

转载注明出处谢谢



传送门1

传送门2

思路:

之前没怎么接触过权值线段树(非主席树),这次就当学习了一下吧。一开始还把题意理解错了,我的天啊……

起初思考了好久,发现不知道怎么处理负数的情况,不过数据里并没有负数?

权值线段树的每个节点表示一个区间[L,R],存储原序列中权值为[L,R]的元素的信息,所以这里的权值线段树每个节点上都是一棵普通线段树,就是负责统计原序列中权值为[L,R]的元素的个数。

每次插入一个相同的值x时就相当于在权值线段树上从根节点一直向下到x所代表的叶子节点,每到权值线段树上的一个节点就修改这个节点所存储的普通线段树的信息

查询时利用二分思想即可(可参见主席树相关练习),但是这里是查询第K大,所以每次折半时计算右子树大小,K大于它就找左边,不然就找右边

注意:

1.代码中采用了标记永久化思想,压小了常数,不过这是我第一次学习使用这个特殊技巧,感觉有些生疏,以后还是要多加练习

(感觉网上对于这个技巧讲述的不多啊!要是那天我熟练了这个技巧了,来写一篇吧

2.BZOJ上计算总和要无符号整型,不然会爆int,我的天啊……

代码:

#include<bits/stdc++.h>
#define M 50005
#define ul unsigned int
using namespace std;
int n,m,cnt_S,cnt_V,root_S[M<<4],root_V;
struct Segment_tree
{
    ul sum,lazy;
    int ch[2];
}S[M*300];
struct Value_tree
{
    int ch[2];
}V[M];
int in()
{
    int t=0,f=1;char ch=getchar();
    while (!isdigit(ch))
    {
        if (ch==‘-‘) f=-1;
        ch=getchar();
    }
    while (isdigit(ch)) t=(t<<3)+(t<<1)+ch-48,ch=getchar();
    return f*t;
}
void S_change(int &rt,int begin,int end,int l,int r)
{
    if (!rt) rt=++cnt_S;
    if (l<=begin&&end<=r)
    {
        S[rt].sum+=end-begin+1;
        S[rt].lazy++;
        return;
    }
    int mid=begin+end>>1;
    if (mid>=l) S_change(S[rt].ch[0],begin,mid,l,r);
    if (mid<r)  S_change(S[rt].ch[1],mid+1,end,l,r);
    S[rt].sum=S[S[rt].ch[0]].sum+S[S[rt].ch[1]].sum+S[rt].lazy*(end-begin+1);
}
ul S_get(int &rt,int begin,int end,int l,int r)
{
    if (!rt) return 0;
    if (l<=begin&&end<=r) return S[rt].sum;
    int mid=begin+end>>1;ul ans=S[rt].lazy*(min(r,end)-max(l,begin)+1);
    if (mid>=l) ans+=S_get(S[rt].ch[0],begin,mid,l,r);
    if (mid<r)  ans+=S_get(S[rt].ch[1],mid+1,end,l,r);
    return ans;
}
void V_change(int &rt,int begin,int end,int x,int y,int z)
{
    if (!rt) rt=++cnt_V;
    S_change(root_S[rt],1,n,x,y);
    if (begin==end) return;
    int mid=begin+end>>1;
    if (mid>=z)
        V_change(V[rt].ch[0],begin,mid,x,y,z);
    else
        V_change(V[rt].ch[1],mid+1,end,x,y,z);
}
int V_get(int rt,int begin,int end,int x,int y,int z)
{
    if (begin==end) return end;
    int mid=begin+end>>1;
    ul t=S_get(root_S[V[rt].ch[1]],1,n,x,y);
    if (z<=t)
        return V_get(V[rt].ch[1],mid+1,end,x,y,z);
    else
        return V_get(V[rt].ch[0],begin,mid,x,y,z-t);
}
main()
{
    n=in();m=in();
    int opt,x,y,z;
    while (m--)
    {
        opt=in();x=in();
        y=in();z=in();
        if (opt==1) V_change(root_V,1,n,x,y,z);
        else printf("%lu\n",V_get(root_V,1,n,x,y,z));
    }
}
时间: 2024-07-29 23:34:05

【BZOJ3110】【codevs1616】K大数查询,权值线段树套普通线段树的相关文章

【BZOJ3110】K大数查询(整体二分)

[BZOJ3110]K大数查询(整体二分) 题面 BZOJ 题解 看了很久整体二分 一直不知道哪里写错了 ... 又把树状数组当成线段树区间加法来用了.. 整体二分还是要想清楚在干什么: 我们考虑第\(K\)大是什么 就是还有\(K-1\)个比他小 这样子就可以考虑二分之后如何\(check\) 当前二分出一个答案之后 按照时间顺序检查每个操作 如果是添加: 如果加进去的值比二分的答案要小 证明对结果没有贡献 直接丢到左区间里不管 否则线段树做区间加法 如果是修改 检查一下当前是否满足 然后分类

【BZOJ-3110】K大数查询 整体二分 + 线段树

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6265  Solved: 2060[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

BZOJ3110 ZJOI2013 K大数查询 线段树套线段树

题意:给定一个数列,维护:1.在a和b之间插入c  2.询问[a,b]中的第c大 题解: 权值线段树套区间线段树 外层的权值线段树中每个节点如果维护[L,R]这个区间,那么该节点所对应的线段树维护的就是[L,R]这些数在每个区间里出现了几次,也就是说如果外层线段树的某个节点维护[L,R],其所对应的内层线段树中某个节点[l,r]维护的值就是[L,R]这些数在[l,r]这个区间中出现的次数. 最后吐槽一下动态内存+指针版线段树MLE……尼玛我写指针版完全习惯了根本就不会写数组版了QAQ,自己拿数据

bzoj3110: [Zjoi2013]K大数查询 【树套树,标记永久化】

好久没写题解了. 但是这题太神了然后做法太神了于是写一下. 这题做法很多,比如黄学长hzw的权值线段树套线段树,比如学长云的bit套主席树(其实是写法更神然后我不会用). 然后看到hzhwcmhf大神题解. http://tieba.baidu.com/p/2246783535 震惊了. 好了开说说做法.建一颗朴素的线段树,树的每个点表示每个区间,然后每个区间建两棵树,一棵是mark树,一棵是all树,两棵都是权值线段树. “mark表示该区间每个点上都会加上mark线段树里的元素 all表示该

bzoj3110: [Zjoi2013]K大数查询

喜闻乐见的简单树套树= =第一维按权值建树状数组,第二维按下标建动态开点线段树,修改相当于第二维区间加,查询在树状数组上二分,比一般的线段树还短= =可惜并不能跑过整体二分= =另外bzoj上的数据有负数= =额其他树套树方法也是可以的爱怎么套怎么套= = #include<cstdio> #define J (i+j>>1) #define I (J+1) typedef unsigned ll; const int N=1e5+5; ll n,m,q,i,j,k,l,s,t,v

[BZOJ3110] [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 [

BZOJ3110 [ZJOI2013] K大数查询(加强数据)

原来的题解:http://www.cnblogs.com/jimzeng/p/bzoj3110.html 有必要特意再写一篇题解…… OrzKPM!KPM加了两组数据结果我原来的代码就被叉了…… 看到数据没有负数KPM就加了负数,然后还卡了long long(极端情况:50000次,每次在1,50000中加入一个同样的数) 需要离散化数据,加long long 然后速度就明显慢了……(9556ms -> 12292ms) 贴代码: 1 #include <iostream> 2 #inc

【ZJOI2013】【BZOJ3110】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

【BZOJ3110】【Zjoi2013】K大数查询 树套树 权值线段树套区间线段树

#include <stdio.h> int main() { puts("转载请注明出处谢谢"); puts("http://blog.csdn.net/vmurder/article/details/43020009"); } 题解: 外层权值线段树,内层区间线段树可解. 权值都是1~n,就不用离散化了. 我写了标记永久化. 其它心得神马的: 天生对树形数据结构无爱. 第一次写树套树,终于知道是怎么回事了. (只针对本题) 就是外层每个点都表示了一段

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

题目大意:有一些位置,这些位置上可以放若干个数字.现在有两种操作. 1.在区间l到r上添加一个数字x 2.求出l到r上的第k大的数字是什么 思路:这种题一看就是树套树,关键是怎么套,怎么写.(话说我也不会来着..)最容易想到的方法就是区间线段树套一个权值线段树,但是区间线段树上的标记就会变得异常复杂.所以我们就反过来套,用权值线段树套区间线段树.这样修改操作在外线段树上就变成了单点修改,外线段树就不用维护标记了.在里面的区间线段树上维护标记就容易多了.具体实现见代码. CODE: #includ