HDU 5919 主席树

题意:

题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=5919
给出n个数字,q个询问,询问在[L,R]区间内,如果有k个不同的数字,那么第(k+1)/2个数字第一次出现的下标是多少。


思路:

一开始以为和spoj的那题一样,只要主席树计算区间种类数,然后二分即可,但是一直T。
其实不用二分,如果按照计算种类数的方式建立可持久化线段树,那么每个版本的线段树上保存的就是当前位置右边所有出现过数字的第一个位置都是1,类似区间找第k大的思路,只要在第L版本的线段树里找到第(k+1)/2个位置的数即可。


代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10;

struct node {
    int ls, rs, sum;
} ns[MAXN * 40];

int rt[MAXN], ct;

void build(int& now, int l, int r) {
    now = ++ct;
    ns[now].sum = 0;
    if (l == r) return;
    int m = (l + r) >> 1;
    build(ns[now].ls, l, m);
    build(ns[now].rs, m + 1, r);
}

void update(int& now, int old, int l, int r, int x, int y) {
    now = ++ct;
    ns[now] = ns[old];
    if (l == r) {
        ns[now].sum += y;
        return;
    }
    int m = (l + r) >> 1;
    if (x <= m) update(ns[now].ls, ns[old].ls, l, m, x, y);
    else update(ns[now].rs, ns[old].rs, m + 1, r, x, y);
    ns[now].sum = ns[ns[now].ls].sum + ns[ns[now].rs].sum;
}

int query(int now, int old, int l, int r, int L, int R) {
    if (L <= l && r <= R) return ns[now].sum - ns[old].sum;
    int m = (l + r) >> 1, res = 0;
    if (L <= m) res += query(ns[now].ls, ns[old].ls, l, m, L, R);
    if (R > m) res += query(ns[now].rs, ns[old].rs, m + 1, r, L, R);
    return res;
}

int solve(int now, int old, int l, int r, int k) {
    if (l == r) return l;
    int m = (l + r) >> 1;
    if (k <= ns[ns[now].ls].sum) return solve(ns[now].ls, ns[old].ls, l, m, k);
    return solve(ns[now].rs, ns[old].rs, m + 1, r, k - ns[ns[now].ls].sum);
}

int n, q;
int pos[MAXN], a[MAXN];

int Scan()
{   //  输入外挂
    int res = 0, flag = 0;
    char ch;
    if ((ch = getchar()) == '-')
    {
        flag = 1;
    }
    else if(ch >= '0' && ch <= '9')
    {
        res = ch - '0';
    }
    while ((ch = getchar()) >= '0' && ch <= '9')
    {
        res = res * 10 + (ch - '0');
    }
    return flag ? -res : res;
}

void Out(int a)
{   //  输出外挂
    if (a < 0)
    {
        putchar('-');
        a = -a;
    }
    if (a >= 10)
    {
       Out(a / 10);
    }
    putchar(a % 10 + '0');
}

int main() {
    //freopen("in.txt", "r", stdin);
    int T, cs = 0;
    T = Scan();
    while (T--) {
        n = Scan(); q = Scan();
        for (int i = 1; i <= n; i++)
            a[i] = Scan();
        ct = 0;
        build(rt[n + 1], 1, n);
        memset(pos, -1, sizeof(pos));
        for (int i = n; i >= 1; i--) {
            update(rt[i], rt[i + 1], 1, n, i, 1);
            if (pos[a[i]] != -1) {
                int tmp;
                update(tmp, rt[i], 1, n, pos[a[i]], -1);
                rt[i] = tmp;
            }
            pos[a[i]] = i;
        }
        printf("Case #%d:", ++cs);
        int ans = 0;
        while (q--) {
            int L, R;
            L = Scan(); R = Scan();
            int LL = min((L + ans) % n + 1, (R + ans) % n + 1);
            int RR = max((L + ans) % n + 1, (R + ans) % n + 1);
            //cout << LL << " " << RR << endl;
            int k = (query(rt[LL], rt[n + 1], 1, n, LL, RR) + 1) / 2;
            ans = solve(rt[LL], rt[n + 1], 1, n, k);
            printf(" ");
            Out(ans);
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值