首页 > 技术文章 > Weed:线段树

hzoi-DeepinC 2019-08-10 17:08 原文

观察复杂度,是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 }
就1.1k但都是精华

 

推荐阅读