魔道研究——综合多种用法的线段树
魔道研究
“我希望能使用更多的魔法。不对,是预定能使用啦。最终我要被大家称呼为大魔法使。为此我决定不惜一切努力。”
——《The Grimoire of Marisa》雾雨魔理沙
魔理沙一如既往地去帕秋莉的大图书馆去借魔导书(Grimoire) 来学习魔道。
最开始的时候,魔理沙只是一本一本地进行研究。然而在符卡战中,魔理沙还是战不过帕秋莉。
好在魔理沙对自己的借还和研究结果进行了记录,从而发现了那些魔导书的精妙之处。
帕秋莉的那些魔导书,每本都有一个类别编号ti 和威力大小pi。而想要获得最有威力的魔法,就必须同时研究一些魔导书。而研究的这些魔导书就必须要满足,类别编号为T 的书的本数小于等于T,并且总共的本数小于等于一个给定的数N。而研究这些魔导书之后习得的魔法的威力就是被研究的魔导书的威力之和。
为了击败帕秋莉,魔理沙想要利用自己发现的规律来获得最有威力的魔法。
她列出了计划中之后M 次的借还事件,并想要知道每个事件之后自己所能获得的魔法的最大威力。可她忙于魔法材料——蘑菇的收集,于是这个问题就交给你来解决了。
输入文件grimoire.in。
第1 行2 个整数N,M,分别表示魔理沙能研究的魔导书本数的上限和她的借还事件数。
之后M 行,每行的形式为“op t p”(不含引号)。Op 为“BORROW” 或“RETURN”,分别表示借书和还书。T 为一个整数,表示这本书的类别编号。P为一个整数,表示这本书的威力大小。注意,还书时如果有多本书满足类别编号为t,威力大小为p,这表明这些书都是相同的,魔理沙会任选其中一本书还回去。如果你问我为何会有相同的书,多半因为这是魔导书吧。
输出文件grimoire.out。
一共M 行,每行一个整数,即每个事件之后的最大威力。
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
5811
10843
5811
11361
14847
9036
3486
8602
18165
18259
对于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.权值线段树:其实就是桶数组的线段树维护嘛。
2.线段树二分:写过线段树的一般都有用过,我是之前没听过这个名字。就是找第K大/小时可以利用线段树本身是二分的性质,开一个cnt数组记录数字个数,接着比较K与左/右儿子的cnt大小 (左/右取决于找的是大还是小),最后进入到相应的左/右儿子中。
3.动态开点:对于我而言是没接触过的东西,不过确实挺好用的。原先的线段树是提前开好点,但当预先开点的数目过多时,就没必要这么做,可以在需要时再开点,一般都是在add里新开点。
现在来口胡一下。这题要维护的是一颗大树的前n大项的和,而大树是由第i棵小树的前i大项组成的。那么先确定这几棵树都为权值线段树,而我们的操作只有简单的放进或去除(是相反的),对于小树肯定是普通的操作,对于大树,我们只用看插入的值与小树中第i大的数的大小关系判断是否需要插入大树,对于删去操作也是类似的思路,可以自己思考一下。。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<queue> 6 #include<algorithm> 7 #include<vector> 8 using namespace std; 9 #define debug printf("zjyvegetable\n") 10 #define ll long long 11 #define mid ((l+r)>>1) 12 inline int read(){ 13 int a=0,b=1;char c=getchar(); 14 while(!isdigit(c)){if(c=='-')b=-1;c=getchar();} 15 while(isdigit(c)){a=a*10+c-'0';c=getchar();} 16 return a*b; 17 } 18 const int N=3e5+50,M=3e7+10,oo=1e9; 19 struct node{ 20 int ls,rs,cnt;ll info; 21 }t[M]; 22 int n,m,tot,root[N];ll ans; 23 void add(int &rt,int l,int r,int key,int z){ 24 if(!rt)rt=++tot; 25 t[rt].cnt+=z;t[rt].info+=key*z; 26 if(l==r)return; 27 if(key<=mid)add(t[rt].ls,l,mid,key,z); 28 else add(t[rt].rs,mid+1,r,key,z); 29 } 30 int ask(int rt,int l,int r,int z){ 31 if(l==r){ 32 ans+=l*min(t[rt].cnt,z);//这个min是弄不满n的的情况 33 return l; 34 } 35 if(z<=t[t[rt].rs].cnt)return ask(t[rt].rs,mid+1,r,z); 36 else{ 37 ans+=t[t[rt].rs].info; 38 return ask(t[rt].ls,l,mid,z-t[t[rt].rs].cnt); 39 } 40 } 41 int main(){ 42 freopen("grimoire.in","r",stdin); 43 freopen("grimoire.out","w",stdout); 44 char s[10];int x,y,z; 45 n=read();m=read(); 46 for(int i=1;i<=m;i++){ 47 scanf("%s",s); 48 x=read();y=read(); 49 if(s[0]=='B'){ 50 z=ask(root[x],0,oo,x); 51 add(root[x],0,oo,y,1); 52 if(z<y){ 53 add(root[0],0,oo,z,-1); 54 add(root[0],0,oo,y,1); 55 } 56 } 57 else{ 58 z=ask(root[x],0,oo,x+1); 59 add(root[x],0,oo,y,-1); 60 if(z<y){ 61 add(root[0],0,oo,y,-1); 62 add(root[0],0,oo,z,1); 63 } 64 } 65 ans=0; 66 ask(root[0],0,oo,n); 67 printf("%lld\n",ans); 68 } 69 return 0; 70 }