[GDOI模拟2016.03.05]魔道研究

Description

给出T个集合,和m个操作,一开始所有集合皆为空。每个操作有两种形式,一是在t集合里加入一个元素x,一种是在t集合里删除一个元素x(保证x存在)。然后从每个集合t中选出前t大的元素加入另一个大集合s中,求s中的前n大元素和。
m,n,t≤300000,所有元素都为不大于109的正整数。

Solution

很明显的数据结构题,关键是要如何维护。想到区间前k大,马上就想到了主席树这种神奇的东西。然而这道题带修改!仔细观察发现其实并不需要主席树的前缀和模式,所以把主席树中的每一颗树分开来就行了。每次操作在单独的一棵线段树上修改。然后判断这个操作对前K大有没有影响,如果有影响就在总的那棵线段树上修改,然后就能过了。
注意动态开节点。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define maxt 1000000007
#define N 300005
#define ll long long 
using namespace std;
struct note{
    int tot,l,r;ll sum;
}t[N*92];
int root[N],n,m,x,y,k,tot;
ll ans;
char s[6];
void add(int &v,int l,int r,int x,int y) {
    if (!v) v=++tot;
    t[v].sum+=x*y;t[v].tot+=y;
    if (l==r) return;
    int m=(l+r)/2;
    if (x<=m) add(t[v].l,l,m,x,y);
    else add(t[v].r,m+1,r,x,y);
}
void find(int v,int l,int r,int x) {
    if (l==r) {k=l;return;}
    int m=(l+r)/2;
    if (t[t[v].r].tot>=x) find(t[v].r,m+1,r,x);
    else find(t[v].l,l,m,x-t[t[v].r].tot);
}
void getans(int v,int l,int r,int x) {
    if (l==r) {ans+=min(x,t[v].tot)*l;return;}
    int m=(l+r)/2;
    if (t[t[v].r].tot>=x) getans(t[v].r,m+1,r,x);
    else ans+=t[t[v].r].sum,getans(t[v].l,l,m,x-t[t[v].r].tot);
}
int main() {
    scanf("%d%d",&n,&m);
    fo(i,1,m) {
        scanf("%s%d%d",s,&x,&y);
        if (s[0]=='B') {
            if (t[root[x]].tot>=x) find(root[x],1,maxt,x);
            else k=0;
            if (y>=k) {
                add(root[0],1,maxt,y,1);
                if (k) add(root[0],1,maxt,k,-1);
            }
            add(root[x],1,maxt,y,1);
        } else {
            if (t[root[x]].tot>x) find(root[x],1,maxt,x+1);
            else k=0;
            if (y>=k) {
                add(root[0],1,maxt,y,-1);
                if (k) add(root[0],1,maxt,k,1);
            }
            add(root[x],1,maxt,y,-1);
        }
        ans=0;getans(root[0],1,maxt,n);
        printf("%lld\n",ans);
    }
}

你可能感兴趣的:(数据结构,线段树,GDOI,魔道研究)