JZOJ 4270 魔道研究

Description

“我希望能使用更多的魔法。不对,是预定能使用啦。最终我要被大家称呼为大魔法使。为此我决定不惜一切努力。”
——《The Grimoire of Marisa》雾雨魔理沙
魔理沙一如既往地去帕秋莉的大图书馆去借魔导书(Grimoire) 来学习魔道。
最开始的时候,魔理沙只是一本一本地进行研究。然而在符卡战中,魔理沙还是战不过帕秋莉。
好在魔理沙对自己的借还和研究结果进行了记录,从而发现了那些魔导书的精妙之处。
帕秋莉的那些魔导书,每本都有一个类别编号ti 和威力大小pi。而想要获得最有威力的魔法,就必须同时研究一些魔导书。而研究的这些魔导书就必须要满足,类别编号为T 的书的本数小于等于T,并且总共的本数小于等于一个给定的数N。而研究这些魔导书之后习得的魔法的威力就是被研究的魔导书的威力之和。
为了击败帕秋莉,魔理沙想要利用自己发现的规律来获得最有威力的魔法。
她列出了计划中之后M 次的借还事件,并想要知道每个事件之后自己所能获得的魔法的最大威力。可她忙于魔法材料——蘑菇的收集,于是这个问题就交给你来解决了。

分析

抽象一下题意。
有n个小集合,每次操作在某个集合中添加或删除某个元素。然后把第i个小集合中的前i大的元素加入大集合。每次操作结束后回答大集合中前n大元素之和。
这道题很直观的想法是用数据结构乱搞来维护。
我们需要支持求和、添加(删除)、找某个集合中K-th Number的功能。
那么权值线段树就能胜任。对于每个集合,我们开一棵权值线段树,区间内保存的是元素在此区间出现的次数。但是不能用堆式存储,显然会爆空间。所以我们需要动态开节点。如果该区间是第一次访问,就新开一个节点存储。
对于添加操作,设在集合i中添加一个权值为x的元素。
首先直接添加。
集合{ a1,a2......ai },若x比ai大,那么在大集合中要把ai删除,把x加入(显然)。
对于删除操作同理,反过来维护即可。
求和也是记录一下区间和随便搞搞就好了。然后就叽里呱啦一通搞定了。注意MemoryLimits。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=300010*90,INF=1000000000;
int tot,root[N],num[N],left[N],right[N];
ll ans,sum[N];
void add(int &v,int l,int r,int k,bool bz)
{
    if(!v) v=++tot;
    if(bz) num[v]++,sum[v]+=k;
    else num[v]--,sum[v]-=k;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(k<=mid) add(left[v],l,mid,k,bz);
    else add(right[v],mid+1,r,k,bz);
}
int query(int v,int l,int r,int k)
{
    if(l==r)
    {
        ans+=min(k,num[v])*l;
        return l;
    }
    int mid=(l+r)>>1;
    if(num[right[v]]>=k) return query(right[v],mid+1,r,k);
    else
    {
        ans+=sum[right[v]];
        return query(left[v],l,mid,k-num[right[v]]);
    }
}
int main()
{
    freopen("grimoire.in","r",stdin);
    freopen("grimoire.out","w",stdout);
    int n,m,x,y;
    char ch;
    scanf("%d %d\n",&n,&m);
    fo(i,1,m)
    {
        scanf("%c",&ch);
        if(ch=='B')
        {
            scanf("ORROW %d %d\n",&x,&y);
            int k=query(root[x],0,INF,x);
            add(root[x],0,INF,y,1);
            if(y>=k)
            {
                add(root[0],0,INF,y,1);
                add(root[0],0,INF,k,0);
            }
        }
        else
        {
            scanf("ETURN %d %d\n",&x,&y);
            int k=query(root[x],0,INF,x+1);
            add(root[x],0,INF,y,0);
            if(y>=k)
            {
                add(root[0],0,INF,y,0);
                add(root[0],0,INF,k,1);
            }
        }
        ans=0;
        query(root[0],0,INF,n);
        printf("%lld\n",ans);
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值