[权值线段树] Jzoj P4270 魔道研究
题解
- 题目大意:有两个操作,①“BORROW”,加入一本i种类的书,它的威力为j ②“RETURN”,换一本种类i威力j的书
- 每次输出前N大的书的威力和(要求i种类的书的数量不能大与i)
- 其实有点像区间第k大的意思,第一眼感觉是数据结构乱搞
- 这题正解是权值线段树+动态开点
- "权值线段树"就是在线段树的基础上用数字当下标
- 考虑一下如何去做
- 对于一次“BORROW”操作,其实就是找到第N个大的,且要合法的
- 如果第N的大的比新加入的点小,可以将第N大的点从前N大踢出,将新点打入
- 对于一次“RETURN”操作,其实就是找到第N+1个大的
- 将其加入,将要还的书踢出(如果在前N大)
- 注意MLE,所以要动态开点
代码
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N=300010*2,inf=1000000000; 5 int tot,root[N],num[N],left[N],right[N]; 6 long long ans,sum[300010]; 7 char ch; 8 void add(int &x,int l,int r,int k,int f) 9 { 10 if (!x) x=++tot; 11 if (f) num[x]++,sum[x]+=k; else num[x]--,sum[x]-=k; 12 if (l==r) return; 13 int mid=(l+r)/2; 14 if (k<=mid) add(left[x],l,mid,k,f); else add(right[x],mid+1,r,k,f); 15 } 16 int query(int x,int l,int r,int k) 17 { 18 if (l==r) 19 { 20 ans+=min(k,num[x])*l; 21 return l; 22 } 23 int mid=(l+r)/2; 24 if (num[right[x]]>=k) return query(right[x],mid+1,r,k); 25 else 26 { 27 ans+=sum[right[x]]; 28 return query(left[x],l,mid,k-num[right[x]]); 29 } 30 } 31 int main() 32 { 33 freopen("grimoire.in","r",stdin); 34 freopen("grimoire.out","w",stdout); 35 int n,m,x,y; 36 scanf("%d%d",&n,&m); 37 for (int i=1;i<=m;i++) 38 { 39 scanf("%c",&ch); 40 if (ch=='B') 41 { 42 scanf("ORROW %d %d\n",&x,&y); 43 int k=query(root[x],0,inf,x); 44 add(root[x],0,inf,y,1); 45 if (y>=k) 46 { 47 add(root[0],0,inf,y,1); 48 add(root[0],0,inf,k,0); 49 } 50 } 51 else 52 { 53 scanf("ETURN %d %d\n",&x,&y); 54 int k=query(root[x],0,inf,x+1); 55 add(root[x],0,inf,y,0); 56 if (y>=k) 57 { 58 add(root[0],0,inf,y,0); 59 add(root[0],0,inf,k,1); 60 } 61 } 62 ans=0; 63 query(root[0],0,inf,n); 64 printf("%lld\n",ans); 65 } 66 return 0; 67 }