Weed:线段树
观察复杂度,是log级别以下回答询问的。
O(1)?逗我kx呢?
自然而然地想到线段树。
学长讲的原题啊考场上还不会打。
线段树上的每个节点都表示一个操作区间。
线段树上维护的权值有3个:这个子区间一共“净”加了多少层cnt,多少量num,以及它需要除掉前面的多少层del。
因为对于每一个子区间,它只可能有几种情况:
新累加了几层,或者这个子区间不够删了删前面的几层,或先删掉前面的几层再加上新的几层。
那么每个叶节点就不用说啦,关键就在于两个区间合并。
1,如果右区间要删左区间而且还能把它删干净,那么整个区间要删的量就是del[r]+del[l]-cnt[l],整个区间累加的量就是右区间所累加的
2,如果删了但没删干净,那么我们就需要删掉左区间里最靠后的几个没被删除的值,删除量就是左区间的删除量,剩余层数就是cnt[l]+cnt[r]-del[r]
但是怎么知道那“最靠后的几个值”呢?
稍微改变一下设问,查询线段树子树中区间内最后几个有权值位置的权值和。打个ask函数就好了。
然而这里有一个易错点:你不能直接查询左子树的最靠后的那几个值,因为右子树可能删除了它的一部分。
所以你要查询的是p子树的后k个的话,实际上就是ask(p,k+del[bro])-ask(p,del[bro])
bro是指p的右兄弟。如果p本身就是右子树则不存在。这样被它右子树删除的元素就被我们考虑到了。
然后就到了考验码力的时候了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define lc p<<1 4 #define rc p<<1|1 5 int opt[200005],k[200005],num[800005],cnt[800005],del[800005],n,m,cl[800005],cr[800005]; 6 int ask(int p,int t){ 7 if(!t)return 0; 8 if(cnt[p]<=t)return num[p]; 9 if(t<=cnt[rc])return ask(rc,t); 10 return ask(lc,t-cnt[rc]+del[rc])-ask(lc,del[rc])+num[rc]; 11 } 12 void up(int p){ 13 if(del[rc]>cnt[lc])del[p]=del[lc]+del[rc]-cnt[lc],cnt[p]=cnt[rc],num[p]=num[rc]; 14 else del[p]=del[lc],cnt[p]=cnt[rc]+cnt[lc]-del[rc],num[p]=num[rc]+num[lc]-ask(lc,del[rc]); 15 } 16 void build(int p,int l,int r){ 17 cl[p]=l;cr[p]=r; 18 if(l==r){ 19 if(opt[l])del[p]=k[l]; 20 else cnt[p]=1,num[p]=k[l]; 21 return; 22 } 23 build(lc,l,l+r>>1);build(rc,(l+r>>1)+1,r);up(p); 24 } 25 void change(int p,int x){ 26 if(cl[p]==cr[p]){ 27 if(opt[x])del[p]=k[x],cnt[p]=num[p]=0; 28 else cnt[p]=1,num[p]=k[x],del[p]=0; 29 return; 30 } 31 if(cl[rc]<=x)change(rc,x);else change(lc,x);up(p); 32 } 33 int main(){ 34 scanf("%d%d",&n,&m); 35 for(int i=1;i<=n;++i)scanf("%d%d",&opt[i],&k[i]); 36 build(1,1,n); 37 for(int i=1,x;i<=m;++i){ 38 scanf("%d",&x);scanf("%d%d",&opt[x],&k[x]); 39 change(1,x);printf("%d\n",num[1]); 40 } 41 }
$Fate \ is \ Fake$