zoukankan      html  css  js  c++  java
  • 数据结构:树套树-树状数组套主席树

    BZOJ1901

    这就是所谓的支持修改的主席树,其实我不太认为这是一个树套树,外面的那层树不够显然

    int n,m,top,tot,sz;
    int v[10005],num[20005],A[10005],B[10005],K[10005],flag[10005],hash[20005],root[10005];
    int sum[2200005],lch[2200005],rch[2200005];
    int L[35],R[35],a,b;

    这个题大概能看出来离线操作的一些端倪来

    top是个游标来指示num数组的,num把题目中涉及到的值都存起来了,然后再用hash离散化处理

    这里的离散化是因为主席树是一个完全二叉树,是对权值的建树,如果不把1,2,4,6,7变成1,2,3,4,5势必要浪费大量的空间

    A,B,K和flag把所有的查询工作存起来了,这也是主席树的特性之一,如果是那种强制在线的题,只能常规树套树了

    root是用来存每一个树状数组节点(C数组)所引出的主席树的根节点,sum,lch和rch应该是线段树家族的常客了

    这里补充一下主席树的基本知识,首先对要进行操作的序列的前缀建权值树。每当询问[l, r]的时候,只要用[1, r]的树减去[1, l-1]的树,然后就可以查找第K小了
    然后对每个前缀建树的时候,只需要新增Logn个点,连到前一个前缀所对应的线段树上,这样就仿佛建了n棵线段树一样
    查询操作的话,我们只要找到对应这个区间的线段树,然后再去按照正常的主席树查询就可以了

    其实查询操作就是正规的主席树查询而已,只不过我们把初始节点也执行了更新操作,这样原树为空,所有的东西就都依托于树状数组了

    这里可以联想一下二维树状数组那里的,我可以维护原始数据的前缀和,只用树状数组维护delta,也可以索性全部update

    如果是索性全部update的话,查询的时候就要先查树状数组找到若干树状数组节点,再查询这些节点对应的主席树了

    int query(int l,int r,int k)
    {
        if(l==r) return l;
        int i,suml=0,sumr=0;
        for(i=1;i<=a;i++) suml+=sum[lch[L[i]]];
        for(i=1;i<=b;i++) sumr+=sum[lch[R[i]]];
        int mid=(l+r)>>1;
        if(sumr-suml>=k)
        {
            for(i=1;i<=a;i++) L[i]=lch[L[i]];
            for(i=1;i<=b;i++) R[i]=lch[R[i]];
            return query(l,mid,k);
        }
        else
        {
            for(i=1;i<=a;i++) L[i]=rch[L[i]];
            for(i=1;i<=b;i++) R[i]=rch[R[i]];
            return query(mid+1,r,k-(sumr-suml));
        }
    }

    最后查的结果是经过了离散化的,别忘了还原

    更新操作就必须完全依赖树状数组了,更新都是先删再加,不管是删还是加,其根本的原理都是执行了一次insert,这样就会在原来的基础上多出logn个节点

    这里我们就想到题目为啥要把所有的东西放在一起了,就是为了一块儿建树,更新的东西在查之前已经准备好了

    再说说可持久化,可持久化是支持查询历史版本的,因为我的原始数据和更新数据都是一起建的,那么我更新之前和更新之后的东西,只要找到对应历史版本的树根查询就可以了

    然后看一下更新操作:

    void update(int last,int l,int r,int &rt,int w,int x)
    {
        rt=++sz;
        sum[rt]=sum[last]+x;lch[rt]=lch[last];rch[rt]=rch[last];
        if(l==r) return;
        int mid=(l+r)>>1;
        if(w<=mid) update(lch[last],l,mid,lch[rt],w,x);
        else update(rch[last],mid+1,r,rch[rt],w,x);
    }

    纯粹的主席树update,只不过需要调用logn次,毕竟有logn个树状数组点嘛

    如果我们把历史版本的root覆盖了,当然就是真的覆盖了,就像本题一样,因为题目也没问历史版本怎么怎么样了

    总的来说,主席树这个东西,值得去细细研究一番

    而且通过这个题,离散化的板子有啦,哈哈

      1 #include<cstdio>
      2 #include<algorithm>
      3 using namespace std;
      4 int n,m,top,tot,sz;
      5 int v[10005],num[20005],A[10005],B[10005],K[10005],flag[10005],hash[20005],root[10005];
      6 int sum[2200005],lch[2200005],rch[2200005];
      7 int L[35],R[35],a,b;
      8 int lowbit(int x)
      9 {
     10     return x&(-x);    
     11 }
     12 int find(int x)
     13 {
     14     int l=1,r=tot,mid;
     15     while(l<=r)
     16     {
     17         int mid=(l+r)>>1;
     18         if(hash[mid]<x) l=mid+1;
     19         else r=mid-1;
     20     }
     21     return l;
     22 }
     23 void update(int last,int l,int r,int &rt,int w,int x)
     24 {
     25     rt=++sz;
     26     sum[rt]=sum[last]+x;lch[rt]=lch[last];rch[rt]=rch[last];
     27     if(l==r) return;
     28     int mid=(l+r)>>1;
     29     if(w<=mid) update(lch[last],l,mid,lch[rt],w,x);
     30     else update(rch[last],mid+1,r,rch[rt],w,x);
     31 }
     32 int query(int l,int r,int k)
     33 {
     34     if(l==r) return l;
     35     int i,suml=0,sumr=0;
     36     for(i=1;i<=a;i++) suml+=sum[lch[L[i]]];
     37     for(i=1;i<=b;i++) sumr+=sum[lch[R[i]]];
     38     int mid=(l+r)>>1;
     39     if(sumr-suml>=k)
     40     {
     41         for(i=1;i<=a;i++) L[i]=lch[L[i]];
     42         for(i=1;i<=b;i++) R[i]=lch[R[i]];
     43         return query(l,mid,k);
     44     }
     45     else
     46     {
     47         for(i=1;i<=a;i++) L[i]=rch[L[i]];
     48         for(i=1;i<=b;i++) R[i]=rch[R[i]];
     49         return query(mid+1,r,k-(sumr-suml));
     50     }
     51 }
     52 //Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数
     53 //C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t
     54 int main()
     55 {
     56     char s[3];
     57     scanf("%d%d",&n,&m);
     58     for(int i=1;i<=n;i++)
     59     {
     60         scanf("%d",&v[i]);
     61         num[++top]=v[i];
     62     }
     63     for(int i=1;i<=m;i++)
     64     {
     65         scanf("%s",s);
     66         scanf("%d%d",&A[i],&B[i]);
     67         if(s[0]=='Q') {scanf("%d",&K[i]);flag[i]=1;}
     68         else num[++top]=B[i];
     69     }
     70     sort(num+1,num+top+1);
     71     hash[++tot]=num[1];
     72     for(int i=2;i<=top;i++)
     73     if(num[i]!=num[i-1])
     74         hash[++tot]=num[i];  //把查询离散化,相同的查询合并
     75     for(int i=1;i<=n;i++)
     76     {
     77         int t=find(v[i]);  //找到v[i]离散化之后的下标 
     78         for(int j=i;j<=n;j+=lowbit(j))
     79             update(root[j],1,tot,root[j],t,1);
     80     }
     81     for(int i=1;i<=m;i++)
     82     if(flag[i])
     83     {
     84         a=0;b=0;A[i]--;  //找到区间所对应的树状数组的根节点? 
     85         for(int j=A[i];j>0;j-=lowbit(j))
     86             L[++a]=root[j];
     87         for(int j=B[i];j>0;j-=lowbit(j))
     88             R[++b]=root[j];
     89         printf("%d
    ",hash[query(1,tot,K[i])]); 
     90     }
     91     else
     92     {
     93         int t=find(v[A[i]]);
     94         for(int j=A[i];j<=n;j+=lowbit(j))
     95             update(root[j],1,tot,root[j],t,-1);
     96         v[A[i]]=B[i];
     97         t=find(B[i]);
     98         for(int j=A[i];j<=n;j+=lowbit(j))
     99             update(root[j],1,tot,root[j],t,1);
    100     }
    101     return 0;
    102 }
  • 相关阅读:
    JAVA代码中加了Try...Catch的执行顺序
    Java的注解机制——Spring自动装配的实现原理
    UWP蓝牙的例子
    MIT License
    Windows 运行时组件
    VS 的编译选项 build下的 platform target -- Any CPU和x86有什么影响?
    swfdump——从内存中提取swf的工具
    生成某一文件夹内文件清单(批量处理)
    统一用户认证和单点登录解决方案
    关键路径计算、总时差、自由时差
  • 原文地址:https://www.cnblogs.com/aininot260/p/9372788.html
Copyright © 2011-2022 走看看