HDU:6703-array(主席树)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6703

题意:现在有一个长度问 n n n的数列,数列里面的数各不相同且都属于 [ 1 , n ] [1,n] [1,n],有 m m m次操作,每次操作包含两种情况,第一种是找一个 p o s pos pos,将第 p o s pos pos个数改成 n u m [ p o s ] + 10 , 000 , 000 num[pos]+10,000,000 num[pos]+10,000,000,第二种是输入两个数 r r r k k k,你需要找到一个不小于 k k k的数并且这个数不能和区间 [ 1 , r ] [1,r] [1,r]内的所有数相同,最后此题强制在线。

解题心得:

  • 这个题要看明白如果将某个数加上 10 , 000 , 000 10,000,000 10,000,000,可以看成这个数无论在哪里将对 k k k不再有任何限制,明白了这个之后思路就很清晰了,答案可能在两部分中,第一部分是被操作过的数,在这些数中找到第一个不小于 k k k的数,第二部分是在 [ r + 1 , n ] [r+1,n] [r+1,n]中找到第一个不小于 k k k的数,在这两个数中取个最小值。
  • 具体实现,对于在 [ r + 1 , n ] [r+1,n] [r+1,n]中找第一个比 k k k大的数可以用主席树解决,对于没有限制的数可以直接用 s e t set set插进去就行了。对于主席树的操作我先找到在区间中小于 k k k的数记为 c n t cnt cnt个,这样我再在区间中找第 c n t + 1 cnt+1 cnt+1个数,这样就找到了第一个大于 k k k的数。
  • 刚开始一眼看出了这个题修改的本质,然后用了两个线段树进行xjb维护,然后xjb寻找,就TLE了。想了想这不是主席树吗,但是修改部分怎么办呢,带修主席树??然后莫名发现用个 s e t set set不就把修改的数解决了吗。


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 7e5+100;
const ll INF = 1e7;

int n, m, num[maxn], lastans, Index, rt[maxn];
set <int> se;

struct Node {
    int cnt, ls, rs;
}node[maxn<<3];

void update(int root) {
    node[root].cnt = node[node[root].ls].cnt + node[node[root].rs].cnt;
}

void insert(int root, int root2, int l, int r, int pos) {
    node[root] = node[root2];
    if(l == r) {
        node[root].cnt++;
        return ;
    }

    int mid = l + r >> 1;
    if(pos <= mid) {
        node[root].ls = ++Index;
        insert(node[root].ls, node[root2].ls, l, mid, pos);
    } else if(pos > mid) {
        node[root].rs = ++Index;
        insert(node[root].rs, node[root2].rs, mid+1, r, pos);
    }

    update(root);
}

void init() {
    Index = 0;
    memset(node, 0, sizeof(node));
    lastans = 0;
    memset(rt, 0, sizeof(rt));
    se.clear();
    scanf("%d%d", &n, &m);
    se.insert(n+1);
    for(int i=1;i<=n;i++) {
        scanf("%d", &num[i]);
        if(rt[i] == 0) rt[i] = ++Index;
        insert(rt[i], rt[i-1], 1, n+1, num[i]);
    }
    //这里插入n+1,即k能取的最大值
    rt[n+1] = ++Index;
    insert(rt[n+1], rt[n], 1, n+1, n+1);
    n++;
    lastans = 0;
}

int get_cnt(int root, int root2, int l, int r, int ql, int qr) {//找到区间内有多少个小于k的数
    if(ql > qr) return 0;
    int mid = l + r >> 1;
    if(l == ql && r == qr) {
        return node[root2].cnt - node[root].cnt;
    }
    if(qr <= mid) return get_cnt(node[root].ls, node[root2].ls, l, mid, ql, qr);
    else if(ql > mid) return get_cnt(node[root].rs, node[root2].rs, mid+1, r, ql, qr);
    else {
        return get_cnt(node[root].ls, node[root2].ls, l, mid, ql, mid) + 
        get_cnt(node[root].rs, node[root2].rs, mid+1, r, mid+1, qr);
    }
}

int query(int root, int root2, int l, int r, int x) {
    if(l == r) {
        return l;
    }
    int chl1 = node[root].ls, chl2 = node[root2].ls;
    int cnt1 = node[chl2].cnt - node[chl1].cnt;
    int mid = l + r >> 1;
    if(cnt1 < x) {
        return query(node[root].rs, node[root2].rs, mid+1, r, x - cnt1);
    } else {
        return query(node[root].ls, node[root2].ls, l, mid, x);
    }
}

int main() {
//    freopen("1.in.txt", "r", stdin);
    int t; scanf("%d", &t);
    while(t--) {
        init();

        for (int i = 1; i <= m; i++) {
            int op;
            scanf("%d", &op);
            if (op == 1) {
                int pos;
                scanf("%d", &pos);
                pos = pos ^ lastans;
                se.insert(num[pos]);
            } else {
                int r, k;
                scanf("%d%d", &r, &k);
                r = r ^ lastans;
                k = k ^ lastans;

                int cnt = get_cnt(rt[r], rt[n], 1, n, 1, k-1);
                int ans1 = INF, ans2;
                if(cnt < (n-r+1))
                    ans1 = query(rt[r], rt[n], 1, n, cnt+1);
                ans2 = (*se.lower_bound(k));
                int ans = min(ans1, ans2);
                lastans = ans;
                printf("%d\n", ans);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值