「划分树」求区间第k大值

思路:将n个数的序列不断划分,根节点是原序列,左子树是原序列排序后较小的一半,右子树是另一半。留意,子数中的元素的相对位置是和父亲序列一样的,见图,这部分参考了这个博客:

「划分树」求区间第k大值_第1张图片

其中,红色标记是进入左子树的元素。

首先,数组序号1开始数,树的层数从0开始数。
有2个辅助数组比较关键:
sorted[]: 是原始数列排序好的数组,用以决定哪些元素要放入左子树。
toLeft[d][i],表示d层[1,i]下标中有多少个元素被置于左子树。以图的0层为例,看toLeft[0],那么应该有:toLeft[0]={1,1,1,2,2,3,3,4}。

建树的时候,toLeft[dep][i]=toLeft[dep][l-1]+lpos-l。含义即dep层至i为止往左子树的元素数相当于早已前往左子树的toLeft[dep][l-1]数量,加上这次for循环新加入的往左的数量。

查询下标计算比较关键。[L,R]是大区间,[l,r]是查询的小区间,先查询[l,r]内有前往左子树的元素数量,记为cnt,若cnt>=k,意味着第k大数必然在左子树,否则去了右子树。

不论去哪,都要重新计算查询的区间。结合图分析计算


若往左。新的左起始下标应该是L+toLeft[dep][l-1]-toLeft[dep][L-1],即seg1区间内往左走的元素,相当于越过seg1前往左子树元素后,才是[l,r]区间里往左走的元素起始点。新的右限自然是newl+cnt-1。

若往右。新的右起始点也应该去除[r, R]区间往左走的元素干扰,所以newr=r+toLeft[dep][R]-toleft[dep][r],确定了右限,而[l,r]又往右去了(r-l+1-cnt)个元素(记为t),那么左起始点就是newr-t+1了。别忘记[l,r]已经有cnt个往左子树走,往右则只需找k-cnt大元素即可。

模板题:poj 2104

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define pii pair
#define ll long long
#define mst(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
int N, M, T;

int tree[20][maxN];
int sorted[maxN];
int toLeft[20][maxN];

void build(int l, int r, int dep) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    int same = mid - l + 1;
    int smid = sorted[mid];
    rep(i, l, r + 1)
        if (tree[dep][i] < smid)
            --same;

    int lpos = l, rpos = mid + 1;
    rep(i, l, r + 1) {
        if (tree[dep][i] < smid) {
            tree[dep + 1][lpos++] = tree[dep][i];
        } else if (tree[dep][i] == smid && same > 0) {
            --same;
            tree[dep + 1][lpos++] = tree[dep][i];
        } else {
            tree[dep + 1][rpos++] = tree[dep][i];
        }
        toLeft[dep][i] = toLeft[dep][l - 1] + lpos - l;
    }
    build(l, mid, dep + 1);
    build(mid + 1, r, dep + 1);
}

// L R是大区间 l,r是查询区间,查询第k大值
int query(int L, int R, int l, int r, int dep, int k) {
    if (l == r) return tree[dep][l];
    int mid = (L + R) >> 1;
    int cnt = toLeft[dep][r] - toLeft[dep][l - 1];
    if (cnt >= k) {
        int newl = L + toLeft[dep][l - 1] - toLeft[dep][L - 1];
        int newr = newl + cnt - 1;
        return query(L, mid, newl, newr, dep + 1, k);
    } else {
        int newr = r + toLeft[dep][R] - toLeft[dep][r];
        int newl = newr - (r - l - cnt);
        return query(mid + 1, R, newl, newr, dep + 1, k - cnt);
    }
}

int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d%d", &N, &M);
    rep(i, 1, N + 1) {
        scanf("%d", &tree[0][i]);
        sorted[i] = tree[0][i];
    }
    sort(sorted + 1, sorted + 1 + N);
    build(1, N, 0);
    int s, t, k;
    rep(i, 0, M) {
        scanf("%d%d%d", &s, &t, &k);
        printf("%d\n", query(1, N, s, t, 0, k));
    }
    return 0;
}

hduoj 3473
有一个正整数序列,给定区间[l,r],找一个整数x使得

最小,求这个最小值。

首先可以分析得知该x即是[l,r]中的中位数。可以推得
ans=rsum-lsum+midVal*(lcnt-rcnt)。
其中rsum是序列中中位数右侧和,lsum对应其左侧和,lcnt是左侧元素个数,rcnt是右侧个数。
留意:找中位数,转右子树操作,此时的cnt是[l,r]内的位于中位数左侧的个数,要和起来,lsum同。

#include 
#include 
using namespace std;

#define pii pair
#define ll long long
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
int N, M, T;
int tree[20][maxN];
int toLeft[20][maxN];
int sorted[maxN];
// leftSum[d][i] 为d层[1,i]进入左子数元素之和
ll leftSum[20][maxN], sum[maxN];
ll lsum, rsum;
int lcnt, rcnt;

void build(int l, int r, int d) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    int same = mid - l + 1;
    int smid = sorted[mid];
    rep(i, l, r + 1)
        if (tree[d][i] < smid)
            --same;

    int lpos = l, rpos = mid + 1;
    rep(i, l, r + 1) {
        if (tree[d][i] < smid) {
            tree[d + 1][lpos++] = tree[d][i];
            leftSum[d][i] = leftSum[d][i - 1] + tree[d][i];
        } else if (tree[d][i] == smid && same > 0) {
            --same;
            tree[d + 1][lpos++] = tree[d][i];
            leftSum[d][i] = leftSum[d][i - 1] + tree[d][i];
        } else {
            tree[d + 1][rpos++] = tree[d][i];
            leftSum[d][i] = leftSum[d][i - 1];
        }
        toLeft[d][i] = toLeft[d][l - 1] + lpos - l;
    }
    build(l, mid, d + 1);
    build(mid + 1, r, d + 1);
}

int query(int L, int R, int l, int r, int d, int k) {
    if (l == r) return tree[d][l];
    
    int mid = (L + R) >> 1;
    int cnt = toLeft[d][r] - toLeft[d][l - 1];
    if (cnt >= k) {
        int newl = L + toLeft[d][l - 1] - toLeft[d][L - 1];
        int newr = newl + cnt - 1;
        return query(L, mid, newl, newr, d + 1, k);
    } else {
        int newr = r + toLeft[d][R] - toLeft[d][r];
        int newl = newr - (r - l - cnt);

        lcnt += cnt;
        lsum += leftSum[d][r] - leftSum[d][l - 1];

        return query(mid + 1, R, newl, newr, d + 1, k - cnt);
    }

}

int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d", &T);
    rep(i, 1, T + 1) {
        sum[0] = 0;

        scanf("%d", &N);
        rep(i, 1, N + 1) {
            scanf("%d", &tree[0][i]);
            sorted[i] = tree[0][i];
            sum[i] = sum[i - 1] + sorted[i];
        }
        sort(sorted + 1, sorted + 1 + N);
        build(1, N, 0);

        printf("Case #%lld:\n", i);
        scanf("%d", &M);
        while (M--) {
            int s, t;
            scanf("%d%d", &s, &t);
            ++s; ++t;
            lcnt = rcnt = 0;
            lsum = 0;
            int mid = (s + t) / 2 - s + 1;
            int midVal = query(1, N, s, t, 0, mid);
            rcnt = t - s + 1 - lcnt;
            rsum = sum[t] - sum[s - 1] - lsum;
            ll ans = rsum - lsum + midVal * (lcnt - rcnt);
            printf("%lld\n", ans);
        }
        puts("");
    }
    return 0;
}

你可能感兴趣的:(「划分树」求区间第k大值)