Jzoj P4270 魔道研究___动态开点+权值线段树

题目大意:

有若干个可重集合,然后我们从第 i 个可重集合中拿前 i 大组成一个新的可重集合 S 。我们的目的是动态维护 S 的前 N 大的和。
给出数 N ,有 M 次操作,每次会插入一个数 p 进入集合 t 中或者在集合 t 中删去原有的一个数 p ,然后回答 S 中的前 N 大的和,不足 N 时直接求当前 S 的总和。

1 <= t , N , M <= 300 000 , 1 <= p <= 1 000 000 000

分析:

我们可以维护所有的小集合以及 S
维护 t 棵权值线段树,第 i 棵线段树对应第 i 个小集合,
另外维护一棵权值线段树,对应 S
对于插入操作,即在树 t 中插入 p
可以通过线段树 O ( l o g n ) 的进行单点修改
然后,
对于要不要将 p 加入 S 中。
这个只要知道 p 在集合 t 中排名是否 > t 大,
而对于第 t 大,我们也可以通过线段树得到
然后我们判断 p 如果 > t 大,那么插入进 S 中,并将原先在 S 的集合 t 的第 t 大的数弹出。
然后对于所有的线段树,我们都要动态开节点,也就是说我们不把那些空节点建起来,只保留那些非空的节点。

P S
我们描述一个可重集合里面的元素,可以开一个以权值为下标的数组,数组内容为对应权值的出现次数。这个数组称为权值数组,而用来维护权值数组的线段树,就是权值线段树。
然后,求某个权值在集合里的排名什么的,只要在线段树里查查前缀和就知道了。
如我们现在站在线段树的某个节点 [ l , r ] 上,我们要求权值在这个区间里的第 k 大元素。
l = r ,显然直接得到为 l
l < r ,我们可以先看一看这个点的右儿子 [ m i d + 1 , r ]
[ m i d + 1 , r ] 中元素个数 k ,那第 k 大元素显然在 [ m i d + 1 , r ] 中,在右儿子求解。
[ m i d + 1 , r ] 中的元素个数 < k ,那么第 k 大元素显然在 [ l , m i d ] ,那么我们令 k 减去 [ m i d + 1 , r ] 中的元素个数,减完后的 k 设为 x
那么我们现在就是要求 [ l , m i d ] 中的第 x 大,那么我们在左儿子求解。

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define fo(i, j, k) for (int i = j; i <= k; i++)
#define N 300005
#define M 50

using namespace std;

const int inf = 1e9;
typedef long long ll;

int n, m, tot, root[N], num[N*M], lson[N*M], rson[N*M];
ll ans,sum[N*M];
char s[10];

void Insert(int &x, int l, int r, ll k, bool flag) {
    if(!x) x = ++tot;
    if (flag) num[x]++, sum[x] += k;
         else num[x]--, sum[x] -= k;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (k <= mid) Insert(lson[x], l, mid, k, flag);
             else Insert(rson[x], mid+1, r, k, flag);
}

int Get_Ranknum(int x, int l, int r, int k) {
    if (l == r) {
        ans += min(k, num[x]) * l;
        return l;
    }

    int mid = (l + r) >> 1;
    if (num[rson[x]] >= k) 
        return Get_Ranknum(rson[x], mid+1, r, k);

    ans += sum[rson[x]];
    return Get_Ranknum(lson[x], l, mid, k - num[rson[x]]);
}

int main() {
    freopen("grimoire.in","r",stdin);
    freopen("grimoire.out","w",stdout);
    scanf("%d %d", &n, &m);
    int x; ll y;
    fo(i, 1, m) {
        scanf("%s", s);
        if (s[0] == 'B') {
            scanf("%d %lld", &x, &y);
            int k = Get_Ranknum(root[x], 0, inf, x);
            Insert(root[x], 0, inf, y, 1);
            if (y >= k) 
                Insert(root[0], 0, inf, y, 1),
                Insert(root[0], 0, inf, k, 0);
        }
        else {
             scanf("%d %lld", &x, &y);
             int k = Get_Ranknum(root[x], 0, inf, x+1);
             Insert(root[x], 0, inf, y, 0);
             if (y >= k) 
                 Insert(root[0], 0, inf, y, 0),
                 Insert(root[0], 0, inf, k, 1);
        }
        ans = 0;
        Get_Ranknum(root[0], 0, inf, n);
        printf("%lld\n", ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/gx_man_vip/article/details/81021822