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

35 篇文章 1 订阅

题目大意:

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

1<=t,N,M<=300000,1<=p<=1000000000

分析:

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

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

代码:

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值