划分树

来源:互联网 发布:如何启动蜂窝移动数据 编辑:程序博客网 时间:2024/06/02 01:52

//xx, 我不是很会主席树, 所以求第k大还是有点遗憾, 所以就学学看这个来求第k大, 只能说这个比主席树好想好敲吧, 听说的是也要比主席树快, 并且空间上也比较省.
这篇博客讲的炒鸡好,不懂的就进去看看
//相信这篇博客已经讲得非常清楚了, 所以这里就只贴下我自己的理解和代码.
以poj – 2104 为例
板子:(老规矩)

/** @Cain*/const int maxn = 1e5+5;int sorted[maxn];int val[20][maxn];int num[20][maxn];void build(int l,int r,int ceng){    if(l == r) return ;    int mid=(l+r) >> 1,issame=mid-l+1;    for(int i=l;i<=r;i++) if(val[ceng][i] < sorted[mid]) issame--;    int ln=l,rn=mid+1;    for(int i=l;i<=r;i++){        if(i==l) num[ceng][i] = 0;        else num[ceng][i] = num[ceng][i-1];        if(val[ceng][i] < sorted[mid] || (val[ceng][i] == sorted[mid] && issame > 0)){            val[ceng+1][ln++] = val[ceng][i];            num[ceng][i]++;            if(val[ceng][i] == sorted[mid]) issame--;        }        else val[ceng+1][rn++] = val[ceng][i];    }    build(l,mid,ceng+1);    build(mid+1,r,ceng+1);}int query(int ceng,int sl,int sr,int l,int r,int k){    if(sl == sr) return val[ceng][sl];    int mid = (sl+sr) >> 1;    int ly;    if( l == sl) ly = 0;    else ly = num[ceng][l-1];    int toleft = num[ceng][r] - ly;    if(toleft >= k) return query(ceng+1,sl,mid,sl+ly,sl+num[ceng][r]-1,k);    else {        int lr = mid + 1 + (l-sl-ly);        return query(ceng+1,mid+1,sr,lr,lr+r-l+1-toleft-1,k-toleft);    }}void solve(){    int n,q; cin >> n >> q;    for(int i=1;i<=n;i++){        scanf("%d",&val[0][i]);        sorted[i] = val[0][i];    }    sort(sorted+1,sorted+n+1);    build(1,n,0);    while(q--){        int l,r,k;        cin >> l >> r >> k;        printf("%d\n",query(0,1,n,l,r,k));    }}

解释版:

/** @Cain*/const int maxn = 1e5+5;int sorted[maxn]; //排序好的数组//是一棵树,但把同一层的放在一个数组里。int val[20][maxn];  //20层,每一层元素排放,0层就是原数组int num[20][maxn];  //num[i] 表示i(包括 i )前面有多少个点进入左孩子void build(int l,int r,int ceng){    if(l == r) return ;    int mid=(l+r) >> 1,issame=mid-l+1;  //isame保存有多少和sorted[mid]一样大的数进入左孩子    //为了让和中位数一样的数平均分在树的两侧.    for(int i=l;i<=r;i++) if(val[ceng][i] < sorted[mid]) issame--;    int ln=l,rn=mid+1;   //本结点两个孩子结点的开头,ln左节点    for(int i=l;i<=r;i++){        if(i==l) num[ceng][i] = 0;        else num[ceng][i] = num[ceng][i-1];        if(val[ceng][i] < sorted[mid] || (val[ceng][i] == sorted[mid] && issame > 0)){            val[ceng+1][ln++] = val[ceng][i];            num[ceng][i]++;            if(val[ceng][i] == sorted[mid]) issame--;        }        else val[ceng+1][rn++] = val[ceng][i];    }    build(l,mid,ceng+1);    build(mid+1,r,ceng+1);}int query(int ceng,int sl,int sr,int l,int r,int k){    if(sl == sr) return val[ceng][sl];    int mid = (sl+sr) >> 1;    int ly;  //ly 表示 l 前面有多少元素进入左孩子    if( l == sl) ly = 0; //和左端点重合时    else ly = num[ceng][l-1];    int toleft = num[ceng][r] - ly;  //这一层l到r之间进入左子树的有toleft个    //为什么可以根据上一层进入的元素推出我们要的区间中的元素的位置    //是因为进入下一层时,我们所需要的那几个元素都是连续的,即都挨着的.    //是利用这个性质然后才进一步推出来的位置.    if(toleft >= k) return query(ceng+1,sl,mid,sl+ly,sl+num[ceng][r]-1,k);    else {        // l-sl 表示l前面有多少数,再减ly 表示这些数中去右子树的有多少个        int lr = mid + 1 + (l-sl-ly);  //l-r 去右边的开头位置        // r-l+1 表示l到r有多少数,减去去左边的,剩下是去右边的,去右边1个,下标就是lr,所以减1        return query(ceng+1,mid+1,sr,lr,lr+r-l+1-toleft-1,k-toleft);    }}void solve(){    int n,q; cin >> n >> q;    for(int i=1;i<=n;i++){        scanf("%d",&val[0][i]);        sorted[i] = val[0][i];    }    sort(sorted+1,sorted+n+1);    build(1,n,0);    while(q--){        int l,r,k;        cin >> l >> r >> k;        printf("%d\n",query(0,1,n,l,r,k));    }}

再给一道模板题 : (验证板子的正确性)
HDU – 4251

原创粉丝点击