HDU 5919 主席树

HDU 5919 主席树

一开始拿到这个题目的时候没啥思路,纠结再三,看了一下网上大佬们的题解,**nb。颠覆了我对主席树题的认知,因此记一下笔记。

题目大意

初始时有一个数列,每次给一个区间,询问该区间内,假设只取区间内的每种数字第一次出现的位置,形成一个新的序列,问这个序列中间数是多少?题目在此。

思路

这道题目巧妙地利用了主席树的保存历史版本的特性:

要点

  1. 主席树上的叶子节点表示的是当前这个位置有几个数字(当然,无论怎么运行都只有0或1);
  2. 在原来主席树建立的基础上添加一个操作:新加入的数如果已经出现过了,将上次出现的位置权值-1,然后再在当前的位置+1,保持树上不存在任何两个位置表示的是同一个数字的出现;
  3. 最最最重要的一点,题目要求的是保留每个数字的第一次出现的位置,那么我们就需要从后往前更新主席树,那么能够保证树上每个1表示的都是[L-n]之间的某个数字的第一次出现。有人可能会疑惑为什么需要从后往前,大家可以自己脑补一下,就能轻易得知从前往后是不可行的;

我们拿题目的样例二来举例子:
一:加入 25

2 5 2 1 2
0 0 0 0 1

二:加入 14

2 5 2 1 2
0 0 0 1 1

三:去除 25,添加23

2 5 2 1 2
0 0 1 1 0

四:添加52

2 5 2 1 2
0 1 1 1 0

五:去除23,添加21

2 5 2 1 2
1 1 0 1 0

比如第一个询问:
[2, 3]会被转化成[3, 4],那么我们只需要看第 3 棵树(因为在此询问中,[1, 2]之间所有的数字的出现是没有意义的)。首先我们要对区间计数(不会区间计数的同学,可以做一下SPOJ D-Query一题),然后获得中位数是k。接下来,只需要在这棵树里直接查询第k小即可(因为能查到第k小的话,一定在[L, R]中,[R-n]之间的值已经超过第k个了),主席树经典用法。

AC代码

#include 

using namespace std;

const int MAXN = 201000;
const int M = MAXN * 50;
int n, q, tot;
int a[MAXN];
int tree[MAXN], lson[M], rson[M], c[M];

int __build(int l, int r) {
    int root = tot++;
    c[root] = 0;
    if (l != r) {
        int mid = (l + r) >> 1;
        lson[root] = __build(l, mid);
        rson[root] = __build(mid + 1, r);
    }
    return root;
}

/**
 * 更新root树表示的历史版本的pos位置加上了val值
 * @param root 树根节点编号
 * @param pos 指定位置
 * @param val 添加的值
 * @return 新树的根节点
 */
int update(int root, int pos, int val) {
    int newroot = tot++, tmp = newroot;
    c[newroot] = c[root] + val;
    int l = 1, r = n;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (pos <= mid) {
            lson[newroot] = tot++;
            rson[newroot] = rson[root];
            newroot = lson[newroot];
            root = lson[root];
            r = mid;
        } else {
            rson[newroot] = tot++;
            lson[newroot] = lson[root];
            newroot = rson[newroot];
            root = rson[root];
            l = mid + 1;
        }
        c[newroot] = c[root] + val;
    }
    return tmp;
}

int query(int root, int pos) {
    int ret = 0;
    int l = 1, r = n;
    while (pos < r) {
        int mid = (l + r) >> 1;
        if (pos <= mid) {
            r = mid;
            root = lson[root];
        } else {
            ret += c[lson[root]];
            root = rson[root];
            l = mid + 1;
        }
    }
    return ret + c[root];
}

/**
 * 求区间第k大
 * @param root
 * @param k
 * @return
 */
int query1(int root, int k) {
    int l, r;
    l = 1, r = n;
    int res = 0;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (c[lson[root]] >= k) {
            r = mid;
            root = lson[root];
        } else if (c[lson[root]] < k) {
            res = l;
            l = mid + 1;
            k -= c[lson[root]];
            root = rson[root];
        }
    }
    return res;
}

void build() {
    tree[n + 1] = __build(1, n);
    map<int, int> mp;
    for (int i = n; i >= 1; i--) {
        if (mp.find(a[i]) == mp.end()) {
            //没找到重复的点,直接更新
            tree[i] = update(tree[i + 1], i, 1);
        } else {
            //先在新树上去掉先前重复的点
            int tmp = update(tree[i + 1], mp[a[i]], -1);
            //在当前位置添加这个点,保持每棵树上不存在重复节点
            tree[i] = update(tmp, i, 1);
        }
        mp[a[i]] = i;
    }
}

int getAns(int l, int r) {
    // 获取不同数的个数
    int k = query(tree[l], r);
    double tmp = k / 2.0;
    k = ceil(tmp);
    return query1(tree[l], k);
}

void reset() {
    n = q = tot = 0;
    memset(a, 0, sizeof(a));
    memset(tree, 0, sizeof(tree));
    memset(lson, 0, sizeof(lson));
    memset(rson, 0, sizeof(rson));
    memset(c, 0, sizeof(c));
}

int main() {
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int t;
    cin >> t;
    for (int tt = 1; tt <= t; tt++) {
        cout << "Case #" << tt << ":";
        cin >> n >> q;
        for (int i = 1; i <= n; i++)
            cin >> a[i];

        build();

        int ans = 0;
        while (q--) {
            int l, r;
            cin >> l >> r;
            int tmpl = l, tmpr = r;
            l = min((tmpl + ans) % n + 1, (tmpr + ans) % n + 1);
            r = max((tmpl + ans) % n + 1, (tmpr + ans) % n + 1);
            ans = getAns(l, r);
            cout << " " << ans;
        }
        cout << endl;

        reset();
    }

    return 0;
}

总结

一开始感觉是非常复杂的一个题,不过看了网上大佬们的操作并理清楚思路之后,感觉真的就是一个模板题,只不过要套两个主席树的用法(哎,自己竟然想了半天没想到,捶桌!)。
细节也不多,主要是二分的地方容易出错。题目数据可能比较水,稍微改了一下板子调整了一下数组大小就A了(中间经历了一发RE一发TLE和一发MLE,枯了)。不过还是要承认,这题是一个为数不多的能颠覆新手对主席树认知的题目。tql!!!

你可能感兴趣的:(HDU 5919 主席树)