BZOJ4378[POI2015]Logistyka——树状数组
题目描述
维护一个长度为n的序列,一开始都是0,支持以下两种操作:
1.U k a 将序列中第k个数修改为a。
2.Z c s 在这个序列上,每次选出c个正数,并将它们都减去1,询问能否进行s次操作。
每次询问独立,即每次询问不会对序列进行修改。
输入
第一行包含两个正整数n,m(1<=n,m<=1000000),分别表示序列长度和操作次数。
接下来m行为m个操作,其中1<=k,c<=n,0<=a<=10^9,1<=s<=10^9。
输出
包含若干行,对于每个Z询问,若可行,输出TAK,否则输出NIE。
样例输入
3 8
U 1 5
U 2 7
Z 2 6
U 3 1
Z 2 6
U 2 2
Z 2 6
Z 2 1
U 1 5
U 2 7
Z 2 6
U 3 1
Z 2 6
U 2 2
Z 2 6
Z 2 1
样例输出
NIE
TAK
NIE
TAK
TAK
NIE
TAK
对于每次询问,设大于等于s的数有k个,那么如果剩下数的和sum>=(c-k)*s,剩下数中每次取最大的(c-k)个就一定能进行s次(证明在最后)。只要离散化一下之后用树状数组维护一下区间个数及区间和就好了。
证明:
首先大于等于s的k个数一定能取s次,设p=c-k,如果取了z次后取不了了,也就是剩下的数不足p个,因为剩下的数之和一定>=p*(s-z),那么剩下的数之中一定有大于s-z的,在取z次之前这个数就大于s了,与上面矛盾,因此只要sum>=p*s就一定能进行s次,反之因为和都小于s,就一定取不了s次。
#include<set> #include<map> #include<queue> #include<cmath> #include<stack> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m; int cnt; char s[20]; int a[1000010]; int b[1000010]; int c[1000010]; int d[1000010]; int e[1000010]; int h[1000010]; struct node { long long v[1000010]; void add(int x,int t) { for(;x<=cnt;x+=x&-x) { v[x]+=t; } } long long query(int x) { long long res=0; for(;x;x-=x&-x) { res+=v[x]; } return res; } }b1,b2; int find(int x) { int l=1,r=cnt,mid; while(l<r) { mid=(l+r)>>1; if(h[mid]<x) { l=mid+1; } else { r=mid; } } return l; } int main() { int num; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%s",s); scanf("%d%d",&b[i],&c[i]); e[i]=c[i]; if(s[0]=='U') { d[i]=1; } } sort(e+1,e+m+1); h[++cnt]=e[1]; for(int i=2;i<=m;i++) { if(e[i]!=e[i-1]) { h[++cnt]=e[i]; } } for(int i=1;i<=m;i++) { c[i]=find(c[i]); } for(int i=1;i<=m;i++) { if(d[i]) { if(num=a[b[i]]) { b1.add(num,-1); b2.add(num,-h[num]); } a[b[i]]=c[i]; b1.add(c[i],1); b2.add(c[i],h[c[i]]); } else { b2.query(c[i]-1)>=(b[i]-b1.query(cnt)+b1.query(c[i]-1))*h[c[i]]?printf("TAK\n"):printf("NIE\n"); } } }