4270. 【NOIP2015模擬10.27】魔道研究

Description

“我希望能使用更多的魔法。不對,是預定能使用啦。最終我要被大家稱呼爲大魔法使。爲此我決定不惜一切努力。”
——《The Grimoire of Marisa》霧雨魔理沙
魔理沙一如既往地去帕秋莉的大圖書館去借魔導書(Grimoire) 來學習魔道。
最開始的時候,魔理沙只是一本一本地進行研究。然而在符卡戰中,魔理沙還是戰不過帕秋莉。
好在魔理沙對自己的借還和研究結果進行了記錄,從而發現了那些魔導書的精妙之處。
帕秋莉的那些魔導書,每本都有一個類別編號ti 和威力大小pi。而想要獲得最有威力的魔法,就必須同時研究一些魔導書。而研究的這些魔導書就必須要滿足,類別編號爲T 的書的本數小於等於T,並且總共的本數小於等於一個給定的數N。而研究這些魔導書之後習得的魔法的威力就是被研究的魔導書的威力之和。
爲了擊敗帕秋莉,魔理沙想要利用自己發現的規律來獲得最有威力的魔法。
她列出了計劃中之後M 次的借還事件,並想要知道每個事件之後自己所能獲得的魔法的最大威力。可她忙於魔法材料——蘑菇的收集,於是這個問題就交給你來解決了。

Input

輸入文件grimoire.in。
第1 行2 個整數N,M,分別表示魔理沙能研究的魔導書本數的上限和她的借還事件數。
之後M 行,每行的形式爲“op t p”(不含引號)。Op 爲“BORROW” 或“RETURN”,分別表示借書和還書。T 爲一個整數,表示這本書的類別編號。P爲一個整數,表示這本書的威力大小。注意,還書時如果有多本書滿足類別編號爲t,威力大小爲p,這表明這些書都是相同的,魔理沙會任選其中一本書還回去。如果你問我爲何會有相同的書,多半因爲這是魔導書吧。

Output

輸出文件grimoire.out。
一共M 行,每行一個整數,即每個事件之後的最大威力。

Sample Input

5 10
BORROW 1 5811
BORROW 3 5032
RETURN 3 5032
BORROW 3 5550
BORROW 5 3486
RETURN 1 5811
RETURN 3 5550
BORROW 4 5116
BORROW 3 9563
BORROW 5 94

Sample Output

5811
10843
5811
11361
14847
9036
3486
8602
18165
18259

Data Constraint

對於5% 的數據,1 <= t,N,M <= 50。
對於10% 的數據,1 <= t,N,M <= 100。
對於30% 的數據,1 <= t,N,M<= 10 000。
另有30% 的數據,1 <= p <= 1 000。
對於100% 的數據,1 <= t,N,M <= 300 000,1<= p<= 1 000 000 000。
另外,總共有30% 的數據,滿足沒有“RETURN” 操作。這部分數據均勻分佈。

思路

1、平衡樹

平衡樹支持查找樹中第i大,還有招它的前驅,所以我們可以找到第i大,然後不停找前驅,這樣就維護了每種系列的最大值。
然後維護包含所有序列樹我們可以用動態規劃(當然太麻煩,而且時間也沒優化多少),所以也可以開個數組存,然後每次就一次快排,當然這樣再一看數據規模,TLE,除非你的常數處理的非常好。所以我們可以再開一棵平衡樹,然後和每個序列的平衡樹對接,這樣就比較優秀了。
而且如果用splay可以省內存,會的人都懂。。。
再jzoj這邊有位同學splay跑了500多ms,而我1000多ms,orz~~~,所以平衡樹還是很好用的,就是代碼量大了點,300多行。當然看個人寫法。

2對頂堆

(⊙o⊙)…這個博主我不會,據說差不多要成爲時代的眼淚了,所以有興趣的的同學請自行百度或谷歌。

3線段樹

這個是用權值線段樹,而且要動態開點,動態開點就是線段樹動態開節點,也就是說我們不把那些空節點建起來,只保留那些非空的節點。
具體來說,假如說,一個用來區間求和的線段樹,初始的時
候序列裏所有元素都是 0, 每個節點就都是空的——全是 0。
我們就沒有必要把這些空節點都建起來——最開始的時候線
段樹就是空的。當我們進行修改操作時,才把這個修改所涉及的
點開出來。
比如,將 3 號位置變爲 4。
我們就把根節點到 [3,3] 節點這條鏈建起來,並進行相應的
修改。
這樣就算序列長度有 1e9 也可以線段樹啦。
不過注意,動態開節點是不能堆式存儲的。

權值線段樹。
我們描述一個可重集合裏面的元素,可以開一個以權值爲下
標的數組,數組內容爲對應權值的出現次數。這個數組稱爲權值
數組。
而用來維護權值數組的線段樹,就是權值線段樹。
然後,求某個權值在集合裏的排名什麼的,只要在線段樹裏
查查前綴和就知道了。

可權值範圍有 1e9 怎麼辦?
不虛!我們可以動態開節點!
當然也可以結合離散化來更好地壓縮空間和時間。

線段樹二分
線段樹本身就是一個二分的結構,我們沒有必要外面
再套一重二分。我們可以直接在線段樹上走。
比如我們現在站在線段樹的某個節點 [l,r] 上,我們要求權值
在這個區間裏的第 k 小元素。
假如 l = r,我們就可以直接報出答案了。
假如 l ̸= r。
我們可以先看一看這個點的左兒子——[l,mid]。假如 [l,mid]
中的元素個數 ≥ k,那第 k 小元素顯然在 [l,mid] 中,我們走到
[l,mid],繼續求解即可。
假如 [l,mid] 中的元素個數 < k,那麼第 k 小元素顯然在
[mid+1,r],也就是右兒子中。我們令 k:=k-[l,mid] 中的元素個數,
走到 [mid+1,r],繼續求解即可。
這樣複雜度是 O(nlogn) 的。
類似地,前 k 小元素的權值和,還有第 k 大,前 k 大之類的
問題都可以這麼解決。

“我們有若干個可重集合,然後我們從第 i 個可重集合中拿前
i 大組成一個新的可重集合 S。我們的目的是動態維護 S 的前 n
大的和。”
顯然,我們需要維護所有的小集合以及 S。
維護 maxt 棵權值線段樹,第 i 棵線段樹對應第 i 個小集合。
我們稱這些線段樹爲小樹。
另外維護一棵權值線段樹,用來維護 S。我們稱這棵線段樹
爲大樹。
對於 BORROW 操作,我們就是在第 t 棵小樹中加入一個元
素 p。這個可以通過線段樹單點修改完成。
之後,我們要確定要不要將 p 加入 S 中。這個只要知道 p
在第 t 個集合中的排名是否 ≤ t 即可。
加入了 S 以後,我們還要將原來的第 t 大,即現在的第 t+1
大從 S 中刪去。
對於 RETURN 操作,我們可以類似地,將 BORROW 操作
取反即可。
而動態維護 S 前 n 大的和自然也是小菜一碟了。

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#include<deque>
#include<map>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define M 300007
#define K 1000000007
#define N 100000
using namespace std;
typedef long long LL;
int i,j,k,l,tt,n,m,p,num;
LL ans,ans1,ans2;
int di[M];
struct node{
    int tot,l,r;
    LL sum;
}t[M*100];
char aa[7];


void dikda(int x,int l,int r,int z){
    if(l==r){
        ans1=l;
        return;
    }
    int mid=(l+r)/2;
    if(t[t[x].r].tot>=z)dikda(t[x].r,mid+1,r,z);
    else dikda(t[x].l,l,mid,z-t[t[x].r].tot);
}
void get(int x,int l,int r,int z){
    if(l==r){
        ans=ans+min(t[x].tot,z)*l;
        return;
    }   
    int mid=(l+r)/2;
    if(t[t[x].r].tot>=z)get(t[x].r,mid+1,r,z);
    else get(t[x].l,l,mid,z-t[t[x].r].tot),ans+=t[t[x].r].sum;
}
int main(){
    freopen("grimoire.in","r",stdin);
    freopen("grimoire.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m){
        scanf("%s%d%d",aa,&tt,&p);
        if(aa[0]=='B'){
            if(t[di[tt]].tot>=tt){
                dikda(di[tt],1,K,tt);
            }
            else ans1=0;
            if(p>=ans1){
                insert(di[0],1,K,p);
                if(ans1>0)delet(di[0],1,K,ans1);
            }
            insert(di[tt],1,K,p);
        }
        else{
            if(t[di[tt]].tot>tt){
                dikda(di[tt],1,K,tt+1);
            }
            else ans1=0;
            if(p>=ans1){
                delet(di[0],1,K,p);
                if(ans1>0)insert(di[0],1,K,ans1);
            }
            delet(di[tt],1,K,p);
        }
        ans=0;
        get(di[0],1,K,n);
        printf("%lld\n",ans);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章