主席树

看了几天学会了模板的写法

主席树主要是用来求区间第k大的 划分树也可以解决 但是划分树只能静态 如果要动态(带修改点) 就可以结合树状数组

还有一类树上主席树 like求两个点之间的第k大点权 就可以用在线lca求出lca来 然后用 x y lca fa[lca] 来做一下加减 求第k大

虽然知道解法但是只写了几道普通的求k大题 数据结构写起来想睡觉 ... 

HDU 5919

从后往前建树 每次都在这颗树中减去上次a[i]出现的index 加上这次的index 这样就保证了 当前的root[i] 里面存的index没有相同的数字 只会保存该数字最左的

如果要求lr的中位数 这时候不用相减 只需要求root[l]这颗线段树中的[l,r]区间的第k大就可以了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define L long long
const int maxn = 200050 ;
int root[maxn] ;
struct node {
    int l , r , sum ;
}T[maxn * 40];
int cnt ;
int n , m ;
int a[maxn];
void build(int l, int r){
    T[++cnt].sum = 0 ;
    T[cnt].l = T[cnt].r = 0;
    if(l == r) return ;
    int mid = (l + r) / 2 ;
    int x = cnt ;
    T[x].l = cnt + 1;
    build(l, mid) ;
    T[x].r = cnt + 1;
    build(mid+1,r) ;
}
int sc[maxn * 2] ;
void upda(int l , int r , int &x , int y , int i , int j ) {
    T[++cnt] = T[y] ;
    x = cnt ;
    if(i >= l && i <= r)
        T[cnt].sum ++;
    if(j != -1 && j >= l && j <= r) T[cnt].sum -- ;
    if(l == r)return ;
    int mid = (l + r) / 2 ;
    if((i >= l && i <= mid) || (j != -1 && j >= l && j <= mid)) {
        upda(l,mid,T[x].l , T[y].l , i , j ) ;
    }
    if((i >= mid+1 && i <= r) || (j != -1 && j >= mid+1 && j <= r)) {
        upda(mid+1,r,T[x].r , T[y].r , i , j ) ;
    }
}

int ans[maxn] ;

int query(int l,int r,int ro,int ll , int rr) {
    if(l <= ll && r >= rr) {
        return T[ro].sum ;
    }
    int mid = ( ll + rr ) / 2 ;
    if(r <= mid) {
        return query(l,r,T[ro].l , ll,mid) ;
    }
    else if(l >= mid + 1) {
        return query(l,r,T[ro].r , mid+1, rr) ;
    }
    else {
        return query(l,r,T[ro].l,ll,mid) + query(l,r,T[ro].r,mid+1,rr) ;
    }
}

int main(){
    int t;
    scanf("%d",&t);
    int cas = 1 ;
    while(t-- ){
        scanf("%d%d",&n,&m) ;
        for(int i = 1; i <= n ;i ++ ){
            scanf("%d",&a[i]);
        }
        root[n+1] = 1;
        cnt = 0 ;
        build(1,n);
        memset(sc , -1 , sizeof(sc)) ;
        for(int i = n ; i >= 1 ; i -- ){
            int x = a[i] ;
            int j = sc[a[i]] ;
            upda(1,n,root[i] ,root[i+1], i , j ) ;
            sc[a[i]] = i ;
        }
        ans[0] = 0 ;
        for(int i = 1; i <= m ; i ++ ) {
            int ll, rr ;
            scanf("%d%d",&ll,&rr);
            int l = min((ll + ans[i-1])%n+1 ,(rr + ans[i-1])%n+1) ;
            int r = max((ll + ans[i-1])%n+1 ,(rr + ans[i-1])%n+1) ;
            int sum = query(l,r,root[l],1,n) ;
            int k = sum / 2;
            if(sum %2 != 0) k ++ ;
            int ro = l ;
            while(l <= r) {
                if(l == r) {
                    ans[i] = l ;
                    break ;
                }
                int mid = (l + r) / 2 ;
                int z = query(l,mid,root[ro],1,n) ;
                if(z >= k ) {
                    r = mid ;
                }
                else {
                    l = mid + 1 ;
                    k -= z ;
                }
            }
        }
        printf("Case #%d: ",cas++ ) ;
        for(int i = 1 ; i <= m ; i ++ ){
            printf("%d",ans[i]);
            if(i == m)printf("
");
            else printf(" ") ;
        }
    }
}

学习数据结构真是一件需要码力的事情 .. 

后来觉得还是应该敲一下练习 于是做了树状数组+线段树

学习了新的写法(kuangbin) 不用递归来做query和update 

zoj2112会出现一种奇妙的错误  "Segmentation Fault" 貌似是由数组下标越界或者堆栈溢出造成的

zoj2112 动态第k大(单点修改)

对于主席树本身 建造一个树林 这个树林是不会变的 对于树状数组 再建一个树林 每次修改 都只在树状数组上做修改

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<map>
#include<vector>
using namespace std;
#define pb push_back
const int maxn = 60050 ;
const int maxnode = 60050 * 40 ;
int T[maxn] ;
int lt[maxnode] , rt[maxnode] , sum[maxnode] ;
int cnt ;
int a[maxn] ;
int S[maxn] ;
struct inp {
    char op[3];
    int a,b,c;
}in[10050];
int n , m ;
vector<int >e ;
int getid(int x){return lower_bound(e.begin(),e.end(),x) - e.begin()+1; } ;
int lowbit(int x){return (x&(-x));} ;
int sz ;
int build(int l,int r) {
    ++cnt ;
    int root = cnt ;
    sum[root] = 0 ;
    if(l < r) {
        int mid = (l + r) / 2;
        lt[root] = build(l,mid);
        rt[root] = build(mid+1,r);
    }
    return root ;
}
int inse(int root , int pos , int val) {
    cnt ++ ;
    int newroot = cnt ; int retu = newroot ;
    sum[newroot] = sum[root] + val ;
    int l = 1 ;
    int r = sz ;
    int mid ;
    while(l < r) {
        mid = (l + r) / 2 ;
        if(pos <= mid) {
            ++cnt ;
            lt[newroot] = cnt , rt[newroot] = rt[root] ;
            newroot = lt[newroot ] , root = lt[root] ;
            r = mid ;
        }
        else {
            ++cnt ;
            rt[newroot] = cnt , lt[newroot] = lt[root] ;
            newroot = rt[newroot] , root = rt[root] ;
            l = mid + 1 ;
        }
        sum[newroot] = sum[root] + val ;
    }
    return retu ;
}
void add(int x , int pos , int val ) {
    while(x <= n ) {
        S[x] = inse(S[x] , pos , val) ;
        x += lowbit(x) ;
    }
    return ;
}
int ll[maxn] , rr[maxn] ;
int lcnt , rcnt ;
int getsum(int x) {
    int res = 0 ;
    if(x == -1) {
        for(int i=1;i<=lcnt ; i ++ ) {
            res += sum[lt[ll[i]]] ;
        }
    }
    else {
        for(int i=1;i<=rcnt ; i ++ ) {
            res += sum[lt[rr[i]]] ;
        }
    }
    return res ;
}
void init() {
    cnt = 0;
    T[0] = build(1,sz) ;
    for(int i = 1;i <= n ; i ++ ){
        T[i] = inse(T[i-1],getid(a[i]),1);
    }
    for(int i = 1;i <= n ; i ++ ){
        S[i]=T[0];
    }
}
int query(int lquery , int rquery , int k ) {
    int l_root , r_root ;
    l_root = T[lquery-1];
    r_root = T[rquery] ;
    int l = 1 , r = sz ;
    lcnt = rcnt = 0 ;
    for(int i = rquery ; i > 0 ; i -= lowbit(i) )rr[++rcnt] = S[i] ;
    for(int i = lquery-1; i > 0 ; i -= lowbit(i) )ll[++lcnt] = S[i] ;
    while(l < r) {
        int mid = (l + r) / 2 ;
        int tmp = getsum(1) - getsum(-1) + sum[lt[r_root]] - sum[lt[l_root]] ;
        if(tmp >= k) {
            for(int i=1;i<=lcnt;i++)ll[i]=lt[ll[i]];
            for(int i=1;i<=rcnt;i++)rr[i]=lt[rr[i]];
            l_root = lt[l_root] ;
            r_root = lt[r_root] ;
            r = mid ;
        }
        else {
            for(int i=1;i<=lcnt;i++)ll[i]=rt[ll[i]];
            for(int i=1;i<=rcnt;i++)rr[i]=rt[rr[i]];
            l_root = rt[l_root] ;
            r_root = rt[r_root] ;
            l = mid + 1;
            k -= tmp ;
        }
    }
    return l ;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m) ;
        e.clear();
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),e.pb(a[i]);
        for(int i=1;i<=m;i++){
            scanf("%s",in[i].op) ;
            scanf("%d%d",&in[i].a ,&in[i].b);
            if(in[i].op[0]=='Q')scanf("%d",&in[i].c);
            else e.pb(in[i].b);
        }
        sort(e.begin(),e.end()); e.erase(unique(e.begin(),e.end()) , e.end()) ;
        sz = e.size() ;
        init();
        for(int i=1;i<=m;i++){
            if(in[i].op[0]=='Q'){
                printf("%d
",e[query(in[i].a,in[i].b,in[i].c) - 1]) ;
            }
            else {
                int pos = in[i].a ;
                int val = in[i].b ;
                int z = a[pos];
                int zid = getid(z) ;
                int valid = getid(val) ;
                add(pos , zid, -1 ) ;
                add(pos , valid , 1 ) ;
                a[pos] = val ;
            }
        }
    }
}

后来学习了一下可持久化并查集 即支持记录每一步中 每一个节点的父亲 并可以合并查询

做法是用可持久化线段树维护一个可持久化数组以实现可持久化并查集

其实还是主席树的应用 不过有一些小细节:

可持久化并查集 路径压缩比较麻烦 所以使用启发式合并 即 每次让rank小的接到rank大的上面 如果rank相同 就让大的rank+1

在维护这个数组的时候 每次query的时候返回下标会比较方便 因为会同时用到一个点的father和rank

bzoj 3674

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;
 
#define pb push_back
#define rep(a,b) for(int i = (a) ; i <= (b) ; i ++)
#define rpe(a,b) for(int i = (a) ; i <= (b) ; i --)
 
int n , m ;
 
struct node {
    int ls , rs ;
    int l , r ;
    int fa ;
    int dep ;
}a[200000 * 50];
 
int cnt ;
int root[200050];
 
int build(int l,int r) {
    ++cnt ;
    a[cnt].l = l ;
    a[cnt].r = r ;
    a[cnt].fa = l ;
    a[cnt].dep = 0 ;
    if(l == r) {
        return cnt;
    }
    int mid = (l + r) >>1 ;
    int z = cnt ;
    a[z].ls = build(l , mid) ;
    a[z].rs = build(mid + 1 , r) ;
    return z ;
}
 
void upda(int x , int &y , int pos , int val) {
    cnt ++ ;
    y = cnt ;
    if(a[x].l == a[x].r) {
        a[y] = a[x] ;
        a[y].fa = val ;
        return ;
    }
    a[y] = a[x] ;
    int mid = (a[y].l + a[y].r) >>1 ;
    if(pos <= mid) {
        upda(a[x].ls , a[y].ls , pos , val) ;
    }
    else {
        upda(a[x].rs , a[y].rs , pos , val) ;
    }
    return ;
}
 
int query(int x , int pos) {
    if(a[x].l == a[x].r) return x ;
    int mid = (a[x].l + a[x].r) >>1 ;
    if(pos <= mid) return query(a[x].ls , pos) ;
    else return query(a[x].rs , pos) ;
}
 
int find_(int x , int pos) {
    int p = query(x , pos) ;
    if(a[p].fa != pos) {
        return find_(x , a[p].fa) ;
    }
    else return p ;
}
 
void add(int x , int pos) {
    if(a[x].l == a[x].r) {
        a[x].dep ++ ;
        return ;
    }
    int mid = (a[x].l + a[x].r) >>1 ;
    if(pos <= mid) add(a[x].ls , pos) ;
    else add(a[x].rs , pos) ;
}
 
int main() {
    while(scanf("%d%d",&n,&m) != EOF) {
        cnt = 0 ;
        root[0] = build(1 , n) ;
        int last = 0 ;
        for(int i = 1 ; i <= m ; i ++ ) {
            int op ;
            scanf("%d",&op) ;
            if(op == 1) {
                int q,w;
                scanf("%d%d",&q,&w) ;
                q ^= last ;
                w ^= last ;
                root[i] = root[i-1] ;
                int pp = find_(root[i] , q) ;
                int qq = find_(root[i] , w) ;
                if(a[pp].dep > a[qq].dep) swap(pp , qq) ;
 
 
                if(a[pp].fa == a[qq].fa) continue ;
 
                upda(root[i-1] , root[i] , a[pp].fa , a[qq].fa) ;
 
                if(a[pp].dep == a[qq].dep) {
                    add(root[i] , a[qq].fa) ;
                }
 
 
            }
            else if(op == 2) {
                int k ;
                scanf("%d",&k) ;
                k ^= last ;
                root[i] = root[k] ;
            }
            else {
                int q,w;
                scanf("%d%d",&q,&w) ;
                q ^= last ; w ^= last ;
                root[i] = root[i-1] ;
                int fq = find_(root[i-1] , q) ;
                int fw = find_(root[i-1] , w) ;
                if(fq != fw) {
                    last = 0 ;
                }
                else last = 1 ;
                printf("%d
",last) ;
            }
        }
    }
}

  

如果数据允许的话 可以分块来做 // 虽然我没有实现过

原文地址:https://www.cnblogs.com/rayrayrainrain/p/6368282.html