[ZOJ2112][可持久化线段树(主席树)][树状数组]Dynamic Rankings[好题]

本文介绍了一种解决区间第k小问题的方法,利用主席树和树状数组结合的技巧来支持单点修改操作,通过离散化处理,有效地解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

    给出一个序列,求区间第k小。要求支持单点修改。

题解:

不谈修改的时候,地球人都会做。
主要考虑如何维护修改。如果直接在建好的线段树上修改,每次需要新建O(n)棵线段树,显然吃不消。
请诸君一定要有一个思想,所谓“函数式线段树”,本质是“化‘树’为‘数’”,让“线段树”这一复杂的结构变得像“数字”一样可以加加减减。为什么区间第k大要用主席树做?因为我们要求区间内小于某一个数的数字个数。这显然是可加的。我们不如就类比做区间求和。带修改的区间求和如何做?树状数组可做。那树状数组每个节点维护的东西变成主席树,就能维护题目中的修改了。

(不要随便看代码。不要随便看代码。不要随便看代码。重要的事情说三遍。)

指针版:

#ifdef _MSC_VER
# include "stdafx.h"
# pragma warning(disable:4996)
#endif

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

//Global Variables & Definitions
#define lowbit(x) ((x) & (-(x)))

int X, M, N;

#define MAXN 50050
#define MAXM 10010
#define MAXT (MAXN + MAXM)

int A[MAXN];
//End Global Variables & Definitions

//Discretization
int w[MAXT];
int wcnt;

void Discre(int cnt) {
    sort(w + 1, w + cnt + 1);
    wcnt = unique(w + 1, w + cnt + 1) - w - 1;
}

int getid(int ww) {
    return lower_bound(w + 1, w + wcnt + 1, ww) - w;
}
//End Discretization

//Chairman Tree
#define DEFINE_MID int mid = (l + r) >> 1
#define lson (u -> lc)
#define rson (u -> rc)

namespace CMT {
    struct node {
        node *lc, *rc;
        int sum;
    } T[1750250];

    int tcnt = 0;
    inline void Init() {
        tcnt = 0;
    }

    inline node* newnode() {
        //return new node();
        return &T[tcnt++];
    }

    inline node* Copy(node *u) {
        node *temp = newnode();
        *temp = *u;
        return temp;
    }

    inline void PushUp(node *u) {
        u->sum = lson->sum + rson->sum;
    }

    void Build(node *u, int l, int r) {
        u->sum = 0; u->lc = u->rc = NULL;

        if (l == r) return;
        DEFINE_MID;

        lson = newnode();
        Build(lson, l, mid);
        rson = newnode();
        Build(rson, mid + 1, r);
    }

    void Insert(node *u, int l, int r, int p, int v = 1) {
        if (l == r) { u->sum += v; return; }

        DEFINE_MID;
        if (p <= mid) {
            lson = Copy(lson);
            Insert(lson, l, mid, p, v);
        }
        else {
            rson = Copy(rson);
            Insert(rson, mid + 1, r, p, v);
        }

        PushUp(u);
    }

    int Query(node *u, int l, int r, int p) {
        if (p >= r) return u->sum;

        DEFINE_MID;

        if (p == mid) return lson->sum;
        else if (p < mid) return Query(lson, l, mid, p);
        else return lson->sum + Query(rson, mid + 1, r, p);
    }
}

using CMT::node;

node* root[MAXN];
//End Chairman Tree

//Fenwick Tree
node* ftr[MAXN];

void Modify(int x, int p, int v) {
    while (x <= N) {
        ftr[x] = CMT::Copy(ftr[x]);
        CMT::Insert(ftr[x], 1, wcnt, p, v);
        x += lowbit(x);
    }
}

int sum(int x, int p) {
    int temp = 0;
    while (x) {
        temp += CMT::Query(ftr[x], 1, wcnt, p);
        x -= lowbit(x);
    }

    return temp;
}
//End Fenwick Tree

//Main Structure
inline int getans(int l, int r, int v) {
    int ansa = CMT::Query(root[r], 1, wcnt, v) - CMT::Query(root[l], 1, wcnt, v);
    ansa += sum(r, v);
    ansa -= sum(l, v);
    return ansa;
}

int Query(int l, int r, int k) {
    int L = 1, R = wcnt;

    while (L < R) {
        int mid = (L + R) >> 1;
        int tans = getans(l - 1, r, mid);

        if (tans >= k) R = mid;
        else L = mid + 1;
    }

    return w[L];
}

char Act[MAXM];
int I[MAXM], J[MAXM], K[MAXM];

void ir() {
    //Read
    scanf("%d%d", &N, &M);

    for (int i = 1; i <= N; ++i) {
        scanf("%d", &A[i]);
        w[i] = A[i];
    }

    wcnt = N;
    char temp[20]; int ii, jj, kk;
    for (int i = 0; i < M; ++i) {
        scanf("%s%d", temp, &ii);

        if ((Act[i] = temp[0]) == 'Q') {
            scanf("%d%d", &jj, &kk);

            I[i] = ii;
            J[i] = jj;
            K[i] = kk;
        }
        else {
            scanf("%d", &jj);

            I[i] = ii;
            w[++wcnt] = J[i] = jj;
        }
    }

    Discre(wcnt);

    //Build
    CMT::Init();
    root[0] = CMT::newnode();
    CMT::Build(root[0], 1, wcnt);
    for (int i = 1; i <= N; ++i) {
        root[i] = CMT::Copy(root[i - 1]);
        CMT::Insert(root[i], 1, wcnt, getid(A[i]));
    }

    for (int i = 1; i <= N; ++i) ftr[i] = root[0];
}

void solve() {
    ir();

    for (int i = 0; i < M; ++i) {
        if (Act[i] == 'Q') {
            printf("%d\n", Query(I[i], J[i], K[i]));
        }
        else {
            Modify(I[i], getid(A[I[i]]), -1);
            Modify(I[i], getid(A[I[i]] = J[i]), 1);
        }
    }
}

inline void g_ir() {
    scanf("%d", &X);
}

int main() {
    g_ir();

    while (X--) solve();
    return 0;
}

数组版:

#ifdef _MSC_VER
# include "stdafx.h"
# pragma warning(disable:4996)
#endif

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

//Global Variables & Definitions
#define lowbit(x) ((x) & (-(x)))

int X, M, N;

#define MAXN 50050
#define MAXM 10010
#define MAXT (MAXN + MAXM)

int A[MAXN];
//End Global Variables & Definitions

//Discretization
int w[MAXT];
int wcnt;

void Discre(int cnt) {
    sort(w + 1, w + cnt + 1);
    wcnt = unique(w + 1, w + cnt + 1) - w - 1;
}

int getid(int ww) {
    return lower_bound(w + 1, w + wcnt + 1, ww) - w;
}
//End Discretization

//Chairman Tree
#define DEFINE_MID int mid = (l + r) >> 1
#define lson T[u].lc
#define rson T[u].rc

namespace CMT {
    struct node {
        int lc, rc;
        int sum;
    } T[2500250];

    int tcnt = 0;
    inline void Init() {
        tcnt = 0;
    }

    inline int newnode() {
        return tcnt++;
    }

    inline int Copy(int u) {
        int temp = newnode();
        T[temp] = T[u];
        return temp;
    }

    inline void PushUp(int u) {
        T[u].sum = T[lson].sum + T[rson].sum;
    }

    void Build(int u, int l, int r) {
        T[u].sum = 0; T[u].lc = T[u].rc = 0;

        if (l == r) return;
        DEFINE_MID;

        lson = newnode();
        Build(lson, l, mid);
        rson = newnode();
        Build(rson, mid + 1, r);
    }

    void Insert(int u, int l, int r, int p, int v = 1) {
        if (l == r) { T[u].sum += v; return; }

        DEFINE_MID;
        if (p <= mid) {
            lson = Copy(lson);
            Insert(lson, l, mid, p, v);
        }
        else {
            rson = Copy(rson);
            Insert(rson, mid + 1, r, p, v);
        }

        PushUp(u);
    }

    int Query(int u, int l, int r, int p) {
        if (p >= r) return T[u].sum;

        DEFINE_MID;

        if (p == mid) return T[lson].sum;
        else if (p < mid) return Query(lson, l, mid, p);
        else return T[lson].sum + Query(rson, mid + 1, r, p);
    }
}

using CMT::node;

int root[MAXN];
//End Chairman Tree

//Fenwick Tree
int ftr[MAXN];

void Modify(int x, int p, int v) {
    while (x <= N) {
        ftr[x] = CMT::Copy(ftr[x]);
        CMT::Insert(ftr[x], 1, wcnt, p, v);
        x += lowbit(x);
    }
}

int sum(int x, int p) {
    int temp = 0;
    while (x) {
        temp += CMT::Query(ftr[x], 1, wcnt, p);
        x -= lowbit(x);
    }

    return temp;
}
//End Fenwick Tree

//Main Structure
inline int getans(int l, int r, int v) {
    int ansa = CMT::Query(root[r], 1, wcnt, v) - CMT::Query(root[l], 1, wcnt, v);
    ansa += sum(r, v);
    ansa -= sum(l, v);
    return ansa;
}

int Query(int l, int r, int k) {
    int L = 1, R = wcnt;

    while (L < R) {
        int mid = (L + R) >> 1;
        int tans = getans(l - 1, r, mid);

        if (tans >= k) R = mid;
        else L = mid + 1;
    }

    return w[L];
}

char Act[MAXM];
int I[MAXM], J[MAXM], K[MAXM];

void ir() {
    //Read
    scanf("%d%d", &N, &M);

    for (int i = 1; i <= N; ++i) {
        scanf("%d", &A[i]);
        w[i] = A[i];
    }

    wcnt = N;
    char temp[20]; int ii, jj, kk;
    for (int i = 0; i < M; ++i) {
        scanf("%s%d", temp, &ii);

        if ((Act[i] = temp[0]) == 'Q') {
            scanf("%d%d", &jj, &kk);

            I[i] = ii;
            J[i] = jj;
            K[i] = kk;
        }
        else {
            scanf("%d", &jj);

            I[i] = ii;
            w[++wcnt] = J[i] = jj;
        }
    }

    Discre(wcnt);

    //Build
    CMT::Init();
    root[0] = CMT::newnode();
    CMT::Build(root[0], 1, wcnt);
    for (int i = 1; i <= N; ++i) {
        root[i] = CMT::Copy(root[i - 1]);
        CMT::Insert(root[i], 1, wcnt, getid(A[i]));
    }

    for (int i = 1; i <= N; ++i) ftr[i] = root[0];
}

void solve() {
    ir();

    for (int i = 0; i < M; ++i) {
        if (Act[i] == 'Q') {
            printf("%d\n", Query(I[i], J[i], K[i]));
        }
        else {
            Modify(I[i], getid(A[I[i]]), -1);
            Modify(I[i], getid(A[I[i]] = J[i]), 1);
        }
    }
}

inline void g_ir() {
    scanf("%d", &X);
}

int main() {
    g_ir();

    while (X--) solve();
    return 0;
}

(当你看到这里我才会告诉你ZOJ上指针版没有WA但是会MLE)

### ZOJ 1088 线段树思路 #### 目概述 ZOJ 1088 是一道涉及动态维护区间的经典问。通常情况下,这类问可以通过线段树来高效解决。目可能涉及到对数组的区间修改以及单点查询或者区间查询。 --- #### 线段树的核心概念 线段树是一种基于分治思想的数据结构,能够快速处理区间上的各种操作,比如求和、最大值/最小值等。其基本原理如下: - **构建阶段**:通过递归方式将原数组划分为多个小区间,并存储在二叉形式的节点中。 - **更新阶段**:当某一段区间被修改时,仅需沿着对应路径向下更新部分节点即可完成全局调整。 - **查询阶段**:利用懒惰标记(Lazy Propagation),可以在 $O(\log n)$ 时间复杂度内完成任意范围内的计算。 具体到本,假设我们需要支持以下两种主要功能: 1. 对指定区间 `[L, R]` 执行某种操作(如增加固定数值 `val`); 2. 查询某一位置或特定区间的属性(如总和或其他统计量)。 以下是针对此场景设计的一种通用实现方案: --- #### 实现代码 (Python) ```python class SegmentTree: def __init__(self, size): self.size = size self.tree_sum = [0] * (4 * size) # 存储区间和 self.lazy_add = [0] * (4 * size) # 延迟更新标志 def push_up(self, node): """ 更新父节点 """ self.tree_sum[node] = self.tree_sum[2*node+1] + self.tree_sum[2*node+2] def build_tree(self, node, start, end, array): """ 构建线段树 """ if start == end: # 到达叶节点 self.tree_sum[node] = array[start] return mid = (start + end) // 2 self.build_tree(2*node+1, start, mid, array) self.build_tree(2*node+2, mid+1, end, array) self.push_up(node) def update_range(self, node, start, end, l, r, val): """ 区间更新 [l,r], 加上 val """ if l <= start and end <= r: # 当前区间完全覆盖目标区间 self.tree_sum[node] += (end - start + 1) * val self.lazy_add[node] += val return mid = (start + end) // 2 if self.lazy_add[node]: # 下传延迟标记 self.lazy_add[2*node+1] += self.lazy_add[node] self.lazy_add[2*node+2] += self.lazy_add[node] self.tree_sum[2*node+1] += (mid - start + 1) * self.lazy_add[node] self.tree_sum[2*node+2] += (end - mid) * self.lazy_add[node] self.lazy_add[node] = 0 if l <= mid: self.update_range(2*node+1, start, mid, l, r, val) if r > mid: self.update_range(2*node+2, mid+1, end, l, r, val) self.push_up(node) def query_sum(self, node, start, end, l, r): """ 查询区间[l,r]的和 """ if l <= start and end <= r: # 完全匹配 return self.tree_sum[node] mid = (start + end) // 2 res = 0 if self.lazy_add[node]: self.lazy_add[2*node+1] += self.lazy_add[node] self.lazy_add[2*node+2] += self.lazy_add[node] self.tree_sum[2*node+1] += (mid - start + 1) * self.lazy_add[node] self.tree_sum[2*node+2] += (end - mid) * self.lazy_add[node] self.lazy_add[node] = 0 if l <= mid: res += self.query_sum(2*node+1, start, mid, l, r) if r > mid: res += self.query_sum(2*node+2, mid+1, end, l, r) return res def solve(): import sys input = sys.stdin.read data = input().split() N, Q = int(data[0]), int(data[1]) # 数组大小 和 操作数量 A = list(map(int, data[2:N+2])) # 初始化数组 st = SegmentTree(N) st.build_tree(0, 0, N-1, A) idx = N + 2 results = [] for _ in range(Q): op_type = data[idx]; idx += 1 L, R = map(int, data[idx:idx+2]); idx += 2 if op_type == 'Q': # 查询[L,R]的和 result = st.query_sum(0, 0, N-1, L-1, R-1) results.append(result) elif op_type == 'U': # 修改[L,R]+X X = int(data[idx]); idx += 1 st.update_range(0, 0, N-1, L-1, R-1, X) print("\n".join(map(str, results))) solve() ``` --- #### 关键点解析 1. **初始化与构建**:在线段树创建过程中,需要遍历输入数据并将其映射至对应的叶子节点[^1]。 2. **延迟传播机制**:为了优化性能,在执行批量更新时不立即作用于所有受影响区域,而是记录更改意图并通过后续访问逐步生效[^2]。 3. **时间复杂度分析**:由于每层最多只访问两个子分支,因此无论是更新还是查询都维持在 $O(\log n)$ 范围内[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值