关于主席树的入门,讲解和题单

主席树真是神仙操作啊……搞了好久才弄懂一点点QAQ

参考文章:https://www.cnblogs.com/zyf0163/p/4749042.html

     https://blog.csdn.net/creatorx/article/details/75446472

     https://blog.csdn.net/jerans/article/details/75807666

     http://www.cnblogs.com/zcysky/p/6832876.html

ps:本文章中的题目我都写过题解了,可以自己去找

 1.前言

据说主席树这个名字的由来呢,是因为创始人的名字缩写hjt与某位相同,然后他因为不会划分树于是自创了这一个数据结构。好强啊orz

主席树能实现什么操作呢?最经典的就是查询区间第k小了,其他的还有诸如树上路径第k小啦,带修改第k小啦之类的。以静态区间第k小为例

 2.定义

先贴一下某神犇对主席树的理解:所谓主席树呢,就是对原来的数列[1..n]的每一个前缀[1..i](1≤i≤n)建立一棵线段树,线段树的每一个节点存某个前缀[1..i]中属于区间[L..R]的数一共有多少个(比如根节点是[1..n],一共i个数,sum[root] = i;根节点的左儿子是[1..(L+R)/2],若不大于(L+R)/2的数有x个,那么sum[root.left] = x)。若要查找[i..j]中第k大数时,设某结点x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在结点x内的数字总数。而对每一个前缀都建一棵树,会MLE,观察到每个[1..i]和[1..i-1]只有一条路是不一样的,那么其他的结点只要用回前一棵树的结点即可,时空复杂度为O(nlogn)。

然而没有什么用,因为感觉根本没看懂

然后来说说我自己的理解吧。如何求出一个区间内第k小呢?直接sort当然可以,但是复杂度爆表。于是我们可以换一个思路,能否将$[l,r]$之间出现过的数都建成线段树呢?设节点为$p$,区间为$[l,r]$,左儿子是$[l,mid]$,右儿子是$[mid+1,r]$

要查找第k大的话,先看左儿子里有多少个数(表示小于等于$mid$的数的个数),如果大于$k$,进左子树找,否则令$k-=左儿子数的个数$,进右子树找

先来考虑一个序列:3,2,1,4

建完树之后是这样的

然后要查第2大,一下子就能发现是2了

(上面画的可能不是很严谨,大家将就下)

但我们不可能对每一个区间都建一棵树,那样的话空间复杂度绝对爆炸

然后可以转化一下思路:前缀和

区间$[l,r]$中小于等于$mid$的数的个数,可以转换为$[1,r]$中小于等于$mid$的数的个数减去$[1,l-1]$中小于等于$mid$的数的个数

于是我们只要对每一个前缀建一棵树即可

然后空间复杂度还是爆炸

然而我们又发现,区间$[1,l-1]$的树和区间$[1,l]$的树最多只会有$log n$个节点不同(因为每次新插入一个节点最多只会更新$log n$个节点),有许多空间是可以重复利用的

只要能将这些空间重复利用起来,就可以解决空间的问题了

还是上面那个序列:3,2,1,4

一开始先建一棵空树,然后一个个把每一个节点加进去

如果要看图的话可以点这里

这个时候有人就要问了,万一序列的数字特别大呢?

当然是离散化

将这些所有值离散一下就行了,可以保证所有数在$1~n$之间

然而感觉讲太多也没啥用……上代码好了,有详细的注释

以区间第k小为例 洛谷p3834

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 200005
 4 using namespace std;
 5 inline int read(){
 6     #define num ch-'0'
 7     char ch;bool flag=0;int res;
 8     while(!isdigit(ch=getchar()))
 9     (ch=='-')&&(flag=true);
10     for(res=num;isdigit(ch=getchar());res=res*10+num);
11     (flag)&&(res=-res);
12     #undef num
13     return res;
14 }
15 int sum[N<<5],L[N<<5],R[N<<5];
16 int a[N],b[N],t[N];
17 int n,q,m,cnt=0;
18 int build(int l,int r){
19     int rt=++cnt;
20     //建树 
21     sum[rt]=0;
22     if(l<r){
23         int mid=(l+r)>>1;
24         L[rt]=build(l,mid);
25         R[rt]=build(mid+1,r);
26     }
27     return rt;
28 }
29 int update(int last,int l,int r,int x){
30     int rt=++cnt;
31     L[rt]=L[last],R[rt]=R[last],sum[rt]=sum[last]+1;
32     //先继承上一次的信息 
33     //L是左节点,R是右节点,sum是节点内数的个数 
34     if(l<r){
35         int mid=(l+r)>>1;
36         if(x<=mid) L[rt]=update(L[last],l,mid,x);
37         else R[rt]=update(R[last],mid+1,r,x);
38         //如果有需要更新的信息,更新
39         //可以发现每一次更新的节点最多只有log n个 
40     }
41     return rt;
42 }
43 int query(int u,int v,int l,int r,int k){
44     if(l>=r) return l;
45     int x=sum[L[v]]-sum[L[u]];
46     //查询操作 
47     int mid=(l+r)>>1;
48     if(x>=k) return query(L[u],L[v],l,mid,k);
49     else return query(R[u],R[v],mid+1,r,k-x);
50     //如果左节点个数大于等于k,进左子树找第k小
51     //否则进右子树 
52 }
53 int main(){
54     //freopen("testdata.in","r",stdin);
55     n=read(),q=read();
56     for(int i=1;i<=n;++i)
57     b[i]=a[i]=read();
58     sort(b+1,b+1+n);
59     m=unique(b+1,b+1+n)-b-1;
60     t[0]=build(1,m);
61     //先建一棵空树 
62     for(int i=1;i<=n;++i){
63         int k=lower_bound(b+1,b+1+m,a[i])-b;
64         //离散 
65         t[i]=update(t[i-1],1,m,k);
66         //然后每次在上一次的基础上建树 
67     }
68     while(q--){
69         int x,y,z;
70         x=read(),y=read(),z=read();
71         int k=query(t[x-1],t[y],1,m,z);
72         printf("%d\n",b[k]);
73     }
74     return 0;
75 }
View Code

如果熟练了之后,可以发现其实第一步的建树过程是可以省略的,直接每一步加节点就行了

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 200005
 4 using namespace std;
 5 inline int read(){
 6     #define num ch-'0'
 7     char ch;bool flag=0;int res;
 8     while(!isdigit(ch=getchar()))
 9     (ch=='-')&&(flag=true);
10     for(res=num;isdigit(ch=getchar());res=res*10+num);
11     (flag)&&(res=-res);
12     #undef num
13     return res;
14 }
15 int sum[N<<5],L[N<<5],R[N<<5];
16 int a[N],b[N],t[N];
17 int n,q,m,cnt=0;
18 void update(int last,int &now,int l,int r,int x){
19     //注意这里开的是引用 
20     if(!now) now=++cnt;
21     sum[now]=sum[last]+1;
22     if(l==r) return;
23     int mid=(l+r)>>1;
24     if(x<=mid) R[now]=R[last],update(L[last],L[now],l,mid,x);
25     else L[now]=L[last],update(R[last],R[now],mid+1,r,x);
26 }
27 int query(int u,int v,int l,int r,int k){
28     if(l>=r) return l;
29     int x=sum[L[v]]-sum[L[u]];
30     int mid=(l+r)>>1;
31     if(x>=k) return query(L[u],L[v],l,mid,k);
32     else return query(R[u],R[v],mid+1,r,k-x);
33 }
34 int main(){
35     //freopen("testdata.in","r",stdin);
36     n=read(),q=read();
37     for(int i=1;i<=n;++i)
38     b[i]=a[i]=read();
39     sort(b+1,b+1+n);
40     m=unique(b+1,b+1+n)-b-1;
41     for(int i=1;i<=n;++i){
42         int k=lower_bound(b+1,b+1+m,a[i])-b;
43         update(t[i-1],t[i],1,m,k);
44         //省略建树过程,直接加入节点 
45     }
46     while(q--){
47         int x,y,z;
48         x=read(),y=read(),z=read();
49         int k=query(t[x-1],t[y],1,m,z);
50         printf("%d\n",b[k]);
51     }
52     return 0;
53 }
View Code

 还有一道板子题洛谷SP3946 poj2104 K-th Number

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 100005
 4 using namespace std;
 5 inline int read(){
 6     #define num ch-'0'
 7     char ch;bool flag=0;int res;
 8     while(!isdigit(ch=getchar()))
 9     (ch=='-')&&(flag=true);
10     for(res=num;isdigit(ch=getchar());res=res*10+num);
11     (flag)&&(res=-res);
12     #undef num
13     return res;
14 }
15 int sum[N<<5],L[N<<5],R[N<<5];
16 int a[N],b[N],rt[N];
17 int n,q,m,cnt=0;
18 void update(int last,int &now,int l,int r,int x){
19     sum[now=++cnt]=sum[last]+1;
20     if(l==r) return;
21     int mid=(l+r)>>1;
22     if(x<=mid) R[now]=R[last],update(L[last],L[now],l,mid,x);
23     else L[now]=L[last],update(R[last],R[now],mid+1,r,x);
24 }
25 int query(int u,int v,int l,int r,int k){
26     if(l>=r) return l;
27     int x=sum[L[v]]-sum[L[u]];
28     int mid=(l+r)>>1;
29     if(x>=k) return query(L[u],L[v],l,mid,k);
30     else return query(R[u],R[v],mid+1,r,k-x);
31 }
32 int main(){
33     //freopen("testdata.in","r",stdin);
34     n=read(),q=read();
35     for(int i=1;i<=n;++i)
36     b[i]=a[i]=read();
37     sort(b+1,b+1+n);
38     m=unique(b+1,b+1+n)-b-1;
39     for(int i=1;i<=n;++i){
40         int k=lower_bound(b+1,b+1+m,a[i])-b;
41         update(rt[i-1],rt[i],1,m,k);
42     }
43     while(q--){
44         int x,y,z;
45         x=read(),y=read(),z=read();
46         int k=query(rt[x-1],rt[y],1,m,z);
47         printf("%d\n",b[k]);
48     }
49     return 0;
50 }
View Code

还有一道题,也是主席树的一般应用 洛谷P3567 [POI2014]KUR-Couriers

出现次数可以转化为左右节点的大小,如果符合条件就递归

题解

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 500005
 4 using namespace std;
 5 inline int read(){
 6     #define num ch-'0'
 7     char ch;bool flag=0;int res;
 8     while(!isdigit(ch=getchar()))
 9     (ch=='-')&&(flag=true);
10     for(res=num;isdigit(ch=getchar());res=res*10+num);
11     (flag)&&(res=-res);
12     #undef num
13     return res;
14 }
15 int sum[N*20],L[N*20],R[N*20],t[N];
16 int n,q,cnt=0;
17 void update(int last,int &now,int l,int r,int x){
18     if(!now) now=++cnt;
19     sum[now]=sum[last]+1;
20     if(l==r) return;
21     int mid=(l+r)>>1;
22     if(x<=mid) R[now]=R[last],update(L[last],L[now],l,mid,x);
23     else L[now]=L[last],update(R[last],R[now],mid+1,r,x);
24 }
25 int query(int u,int v,int l,int r,int k){
26     if(l==r) return l;
27     int x=sum[L[v]]-sum[L[u]],y=sum[R[v]]-sum[R[u]];
28     int mid=(l+r)>>1;
29     if(x*2>k) return query(L[u],L[v],l,mid,k);
30     if(y*2>k) return query(R[u],R[v],mid+1,r,k);
31     return 0;
32 }
33 int main(){
34     //freopen("testdata.in","r",stdin);
35     n=read(),q=read();
36     for(int i=1;i<=n;++i){
37         int x=read();
38         update(t[i-1],t[i],1,n,x);
39     }
40     while(q--){
41         int x,y;
42         x=read(),y=read();
43         int k=query(t[x-1],t[y],1,n,y-x+1);
44         printf("%d\n",k);
45     }
46     return 0;
47 }
View Code

然后区间静态第k大就解决了~\(≧▽≦)/~啦啦啦

树上路径

有些题目会给你一棵树,问你树上两点间路径上的第k大

怎么解决呢?

可以发现,这个东西是可以进行差分的

比如说,$u$到$v$路径上的权值和,可以变成$sum[u]+sum[v]-sum[lca]-sum[lca_fa]$

然后套到主席树上,就是小于某个数的个数,同样也可以差分出来表示

但问题是主席树怎么建呢?

我们发现,因为要求lca,我们可以在树剖dfs的时候顺便加点

具体来说,就是用$fa[i]$的信息更新$i$点的信息

以bzoj2588 洛谷p2633. count on a tree为例

题解

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 100005
 4 #define M 2000005
 5 using namespace std;
 6 inline int read(){
 7     #define num ch-'0'
 8     char ch;bool flag=0;int res;
 9     while(!isdigit(ch=getchar()))
10     (ch=='-')&&(flag=true);
11     for(res=num;isdigit(ch=getchar());res=res*10+num);
12     (flag)&&(res=-res);
13     #undef num
14     return res;
15 }
16 int sum[M],L[M],R[M];
17 int a[N],b[N],rt[N];
18 int fa[N],sz[N],d[N],ver[N<<1],Next[N<<1],head[N],son[N],top[N];
19 int n,q,m,cnt=0,tot=0,ans=0;
20 void update(int last,int &now,int l,int r,int x){
21     sum[now=++cnt]=sum[last]+1;
22     if(l==r) return;
23     int mid=(l+r)>>1;
24     if(x<=mid) R[now]=R[last],update(L[last],L[now],l,mid,x);
25     else L[now]=L[last],update(R[last],R[now],mid+1,r,x);
26 }
27 inline void add(int u,int v){
28     ver[++tot]=v,Next[tot]=head[u],head[u]=tot;
29     ver[++tot]=u,Next[tot]=head[v],head[v]=tot;
30 }
31 void dfs(int u){
32     sz[u]=1,d[u]=d[fa[u]]+1;
33     update(rt[fa[u]],rt[u],1,m,a[u]);
34     for(int i=head[u];i;i=Next[i]){
35         int v=ver[i];
36         if(v==fa[u]) continue;
37         fa[v]=u,dfs(v);
38         sz[u]+=sz[v];
39         if(!son[u]||sz[v]>sz[son[u]]) son[u]=v;
40     }
41 }
42 void dfs(int u,int tp){
43     top[u]=tp;
44     if(!son[u]) return;
45     dfs(son[u],tp);
46     for(int i=head[u];i;i=Next[i]){
47         int v=ver[i];
48         if(v==son[u]||v==fa[u]) continue;
49         dfs(v,v);
50     }
51 }
52 int LCA(int x,int y){
53     while(top[x]!=top[y])
54     d[top[x]]>=d[top[y]]?x=fa[top[x]]:y=fa[top[y]];
55     return d[x]>=d[y]?y:x;
56 }
57 int query(int ql,int qr,int lca,int lca_fa,int l,int r,int k){
58     if(l>=r) return l;
59     int x=sum[L[ql]]+sum[L[qr]]-sum[L[lca]]-sum[L[lca_fa]];
60     int mid=(l+r)>>1;
61     if(x>=k) return query(L[ql],L[qr],L[lca],L[lca_fa],l,mid,k);
62     else return query(R[ql],R[qr],R[lca],R[lca_fa],mid+1,r,k-x);
63 }
64 int main(){
65     //freopen("testdata.in","r",stdin);
66     n=read(),q=read();
67     for(int i=1;i<=n;++i)
68     b[i]=a[i]=read();
69     sort(b+1,b+1+n);
70     m=unique(b+1,b+1+n)-b-1;
71     for(int i=1;i<=n;++i)
72     a[i]=lower_bound(b+1,b+1+m,a[i])-b;
73     for(int i=1;i<n;++i){
74         int u=read(),v=read();
75         add(u,v);
76     }
77     dfs(1),dfs(1,1);
78     while(q--){
79         int x,y,z,lca;
80         x=read(),y=read(),z=read();
81         x^=ans,lca=LCA(x,y);
82         ans=b[query(rt[x],rt[y],rt[lca],rt[fa[lca]],1,m,z)];
83         printf("%d\n",ans);
84     }
85     return 0;
86 }
View Code

 还有一题[bzoj3123][洛谷P3302] [SDOI2013]森林

路经查询就是主席树维护,而连接两棵树就是用启发式合并

题解

  1 //minamoto
  2 #include<bits/stdc++.h>
  3 using namespace std;
  4 inline int read(){
  5     #define num ch-'0'
  6     char ch;bool flag=0;int res;
  7     while(!isdigit(ch=getchar()))
  8     (ch=='-')&&(flag=true);
  9     for(res=num;isdigit(ch=getchar());res=res*10+num);
 10     (flag)&&(res=-res);
 11     #undef num
 12     return res;
 13 }
 14 const int N=80005,M=N*200;
 15 int ver[N<<2],Next[N<<2],head[N];
 16 int a[N],fa[N],sz[N],b[N];
 17 int n,m,tot,q,size,ans;
 18 void add(int u,int v){
 19     ver[++tot]=v,Next[tot]=head[u],head[u]=tot;
 20     ver[++tot]=u,Next[tot]=head[v],head[v]=tot;
 21 }
 22 int L[M],R[M],sum[M],rt[N],cnt;
 23 void update(int last,int &now,int l,int r,int x){
 24     sum[now=++cnt]=sum[last]+1;
 25     if(l==r) return;
 26     int mid=(l+r)>>1;
 27     if(x<=mid) R[now]=R[last],update(L[last],L[now],l,mid,x);
 28     else L[now]=L[last],update(R[last],R[now],mid+1,r,x);
 29 }
 30 int query(int u,int v,int lca,int lca_fa,int l,int r,int k){
 31     if(l>=r) return l;
 32     int x=sum[L[v]]+sum[L[u]]-sum[L[lca]]-sum[L[lca_fa]];
 33     int mid=(l+r)>>1;
 34     if(x>=k) return query(L[u],L[v],L[lca],L[lca_fa],l,mid,k);
 35     else return query(R[u],R[v],R[lca],R[lca_fa],mid+1,r,k-x);
 36 }
 37 inline int hash(int x){
 38     return lower_bound(b+1,b+1+size,x)-b;
 39 }
 40 int ff(int x){
 41     return fa[x]==x?x:fa[x]=ff(fa[x]);
 42 }
 43 int st[N][17],d[N],vis[N];
 44 void dfs(int u,int father,int root){
 45     st[u][0]=father;
 46     for(int i=1;i<=16;++i)
 47     st[u][i]=st[st[u][i-1]][i-1];
 48     ++sz[root];
 49     d[u]=d[father]+1;
 50     fa[u]=root;
 51     vis[u]=1;
 52     update(rt[father],rt[u],1,size,hash(a[u]));
 53     for(int i=head[u];i;i=Next[i]){
 54         int v=ver[i];
 55         if(v==father) continue;
 56         dfs(v,u,root);
 57     }
 58 }
 59 int LCA(int x,int y){
 60     if(x==y) return x;
 61     if(d[x]<d[y]) swap(x,y);
 62     for(int i=16;i>=0;--i){
 63         if(d[st[x][i]]>=d[y]) x=st[x][i];
 64     }
 65     if(x==y) return x;
 66     for(int i=16;i>=0;--i){
 67         if(st[x][i]!=st[y][i])
 68         x=st[x][i],y=st[y][i];
 69     }
 70     return st[x][0];
 71 }
 72 int main(){
 73     //freopen("testdata.in","r",stdin);
 74     int t=read();
 75     n=read(),m=read(),q=read();
 76     for(int i=1;i<=n;++i)
 77     a[i]=b[i]=read(),fa[i]=i;
 78     sort(b+1,b+1+n);
 79     size=unique(b+1,b+1+n)-b-1;
 80     for(int i=1;i<=m;++i){
 81         int u=read(),v=read();
 82         add(u,v);
 83     }
 84     for(int i=1;i<=n;++i)
 85     if(!vis[i]) dfs(i,0,i);
 86     while(q--){
 87         char ch;int x,y;
 88         while(!isupper(ch=getchar()));
 89         x=read()^ans,y=read()^ans;
 90         if(ch=='Q'){
 91             int k=read()^ans;
 92             int lca=LCA(x,y);
 93             ans=b[query(rt[x],rt[y],rt[lca],rt[st[lca][0]],1,size,k)];
 94             printf("%d\n",ans);
 95         }
 96         else{
 97             add(x,y);
 98             int u=ff(x),v=ff(y);
 99             if(sz[u]<sz[v]) swap(x,y),swap(u,v);
100             dfs(y,x,u);
101         }
102     }
103     return 0;
104 }
View Code

洛谷P3066 [USACO12DEC]逃跑的BarnRunning Away From…

要对每一个子树进行操作,怎么做呢?

我们可以直接dfs这棵树,并记录下进入一个点的编号$l[i]$和从这个点出去时的编号$r[i]$

那么这个点的子树的区间一定是$[l[i],r[i]]$

然后直接在树上查询就行了

题解

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 200005
 4 #define M 4000005
 5 #define ll long long
 6 #define inf 0x3f3f3f3f
 7 using namespace std;
 8 inline ll read(){
 9     #define num ch-'0'
10     char ch;bool flag=0;ll res;
11     while(!isdigit(ch=getchar()))
12     (ch=='-')&&(flag=true);
13     for(res=num;isdigit(ch=getchar());res=res*10+num);
14     (flag)&&(res=-res);
15     #undef num
16     return res;
17 }
18 int sum[M],L[M],R[M],rt[N];
19 int ver[N<<1],Next[N<<1],head[N];ll edge[N<<1];
20 int ls[N],rs[N];ll a[N],b[N];
21 int n,m,cnt,tot;ll p;
22 void update(int last,int &now,int l,int r,int x){
23     sum[now=++cnt]=sum[last]+1;
24     if(l==r) return;
25     int mid=(l+r)>>1;
26     if(x<=mid) R[now]=R[last],update(L[last],L[now],l,mid,x);
27     else L[now]=L[last],update(R[last],R[now],mid+1,r,x);
28 }
29 int query(int u,int v,int l,int r,int k){
30     if(r<k) return sum[v]-sum[u];
31     if(l>=k) return 0;
32     int mid=(l+r)>>1;
33     if(k<=mid) return query(L[u],L[v],l,mid,k);
34     else return query(R[u],R[v],mid+1,r,k)+sum[L[v]]-sum[L[u]];
35 }
36 inline void add(int u,int v,ll e){
37     ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e;
38 }
39 void dfs(int u,int fa,ll d){
40     b[ls[u]=++m]=d,a[m]=d;
41     for(int i=head[u];i;i=Next[i])
42     if(ver[i]!=fa) dfs(ver[i],u,d+edge[i]);
43     rs[u]=m;
44 }
45 int main(){
46     n=read(),p=read();
47     for(int u=2;u<=n;++u){
48         int v=read();ll e=read();
49         add(v,u,e);
50     }
51     dfs(1,0,0);
52     sort(b+1,b+1+m);
53     m=unique(b+1,b+1+m)-b-1;
54     for(int i=1;i<=n;++i){
55         int k=lower_bound(b+1,b+1+m,a[i])-b;
56         update(rt[i-1],rt[i],1,m,k);
57     }
58     b[m+1]=inf;
59     for(int i=1;i<=n;++i){
60         int k=upper_bound(b+1,b+2+m,a[ls[i]]+p)-b;
61         k=query(rt[ls[i]-1],rt[rs[i]],1,m,k);
62         printf("%d\n",k);
63     }
64     return 0;
65 }
View Code

带修改主席树

我们可以发现,主席树每一棵线段树维护的都是一个前缀和

如果有修改操作,每一次都要对后面的所有的前缀和都进行修改,那样的话时间复杂度就太爆炸了

我们可以考虑一下树状数组

树状数组维护的也是前缀和,但它的每一次修改是$O(log n)$的

他的节点存的并不是前缀和,但我们仍可以用树状数组来求出前缀和

于是我们可以用树状数组的思想来维护,主席树

用树状数组存一下每个节点的位置,每一次修改都按树状数组的方法去修改,也就是说并不需要修改那么多节点

查询的时候,也按树状数组的方法查询就好了

建议对这段话仔细理解,我当初也是懵逼了好久,最后看了zcysky大佬的那篇blog才蓦然醒悟的

拿bzoj1901洛谷P2617 Dynamic Rankings为例

是一个带修改主席树的板子

思路就按我上面所说的

题解

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 10005
 4 using namespace std;
 5 inline int read(){
 6     #define num ch-'0'
 7     char ch;bool flag=0;int res;
 8     while(!isdigit(ch=getchar()))
 9     (ch=='-')&&(flag=true);
10     for(res=num;isdigit(ch=getchar());res=res*10+num);
11     (flag)&&(res=-res);
12     #undef num
13     return res;
14 }
15 inline int lowbit(int x){return x&(-x);}
16 int sum[N*600],L[N*600],R[N*600];
17 int xx[N],yy[N],rt[N],a[N],b[N<<1],ca[N],cb[N],cc[N];
18 int n,q,m,cnt=0,totx,toty;
19 void update(int last,int &now,int l,int r,int x,int v){
20     sum[now=++cnt]=sum[last]+v;
21     L[now]=L[last],R[now]=R[last];
22     if(l==r) return;
23     int mid=(l+r)>>1;
24     if(x<=mid) update(L[last],L[now],l,mid,x,v);
25     else update(R[last],R[now],mid+1,r,x,v);
26 }
27 int query(int l,int r,int q){
28     if(l==r) return l;
29     int x=0,mid=(l+r)>>1;
30     for(int i=1;i<=totx;++i) x-=sum[L[xx[i]]];
31     for(int i=1;i<=toty;++i) x+=sum[L[yy[i]]];
32     if(q<=x){
33         for(int i=1;i<=totx;++i) xx[i]=L[xx[i]];
34         for(int i=1;i<=toty;++i) yy[i]=L[yy[i]];
35         return query(l,mid,q);
36     }
37     else{
38         for(int i=1;i<=totx;++i) xx[i]=R[xx[i]];
39         for(int i=1;i<=toty;++i) yy[i]=R[yy[i]];
40         return query(mid+1,r,q-x);
41     }
42 }
43 void add(int x,int y){
44     int k=lower_bound(b+1,b+1+m,a[x])-b;
45     for(int i=x;i<=n;i+=lowbit(i)) update(rt[i],rt[i],1,m,k,y);
46 }
47 int main(){
48     //freopen("testdata.in","r",stdin);
49     n=read(),q=read();
50     for(int i=1;i<=n;++i)
51     b[++m]=a[i]=read();
52     for(int i=1;i<=q;++i){
53         char ch;
54         while(!isupper(ch=getchar()));
55         ca[i]=read(),cb[i]=read();
56         if(ch=='Q') cc[i]=read();else b[++m]=cb[i];
57     }
58     sort(b+1,b+1+m);
59     m=unique(b+1,b+1+m)-b-1;
60     for(int i=1;i<=n;++i) add(i,1);
61     for(int i=1;i<=q;++i){
62         if(cc[i]){
63             totx=toty=0;
64             for(int j=ca[i]-1;j;j-=lowbit(j)) xx[++totx]=rt[j];
65             for(int j=cb[i];j;j-=lowbit(j)) yy[++toty]=rt[j];
66             printf("%d\n",b[query(1,m,cc[i])]);
67         }
68         else{add(ca[i],-1),a[ca[i]]=cb[i],add(ca[i],1);}
69     }
70     return 0;
71 }
View Code

还有一道[BZOJ3295] [Cqoi2011]洛谷p3157动态逆序对

题解

 1 //minamoto
 2 #include<bits/stdc++.h>
 3 #define N 100005
 4 #define M 5000005
 5 #define ll long long
 6 using namespace std;
 7 inline ll read(){
 8     #define num ch-'0'
 9     char ch;bool flag=0;ll res;
10     while(!isdigit(ch=getchar()))
11     (ch=='-')&&(flag=true);
12     for(res=num;isdigit(ch=getchar());res=res*10+num);
13     (flag)&&(res=-res);
14     #undef num
15     return res;
16 }
17 int L[M],R[M],sum[M],rt[N];
18 int val[N],pos[N],xx[N],yy[N],c[N],a1[N],a2[N];
19 int n,cnt,q;ll ans=0;
20 inline int lowbit(int x){return x&(-x);}
21 int ask(int x){
22     int s=0;
23     for(int i=x;i;i-=lowbit(i)) s+=c[i];
24     return s;
25 }
26 void update(int &now,int l,int r,int k){
27     if(!now) now=++cnt;
28     ++sum[now];
29     if(l==r) return;
30     int mid=(l+r)>>1;
31     if(k<=mid) update(L[now],l,mid,k);
32     else update(R[now],mid+1,r,k);
33 }
34 int querysub(int x,int y,int v){
35     int cntx=0,cnty=0,ans=0;--x;
36     for(int i=x;i;i-=lowbit(i)) xx[++cntx]=rt[i];
37     for(int i=y;i;i-=lowbit(i)) yy[++cnty]=rt[i];
38     int l=1,r=n;
39     while(l<r){
40         int mid=(l+r)>>1;
41         if(v<=mid){
42             for(int i=1;i<=cntx;++i) ans-=sum[R[xx[i]]];
43             for(int i=1;i<=cnty;++i) ans+=sum[R[yy[i]]];
44             for(int i=1;i<=cntx;++i) xx[i]=L[xx[i]];
45             for(int i=1;i<=cnty;++i) yy[i]=L[yy[i]];
46             r=mid;
47         }
48         else{
49             for(int i=1;i<=cntx;++i) xx[i]=R[xx[i]];
50             for(int i=1;i<=cnty;++i) yy[i]=R[yy[i]];
51             l=mid+1;
52         }
53     }
54     return ans;
55 }
56 int querypre(int x,int y,int v){
57     int cntx=0,cnty=0,ans=0;--x;
58     for(int i=x;i;i-=lowbit(i)) xx[++cntx]=rt[i];
59     for(int i=y;i;i-=lowbit(i)) yy[++cnty]=rt[i];
60     int l=1,r=n;
61     while(l<r){
62         int mid=(l+r)>>1;
63         if(v>mid){
64             for(int i=1;i<=cntx;++i) ans-=sum[L[xx[i]]];
65             for(int i=1;i<=cnty;++i) ans+=sum[L[yy[i]]];
66             for(int i=1;i<=cntx;++i) xx[i]=R[xx[i]];
67             for(int i=1;i<=cnty;++i) yy[i]=R[yy[i]];
68             l=mid+1;
69         }
70         else{
71             for(int i=1;i<=cntx;++i) xx[i]=L[xx[i]];
72             for(int i=1;i<=cnty;++i) yy[i]=L[yy[i]];
73             r=mid;
74         }
75     }
76     return ans;
77 }
78 int main(){
79     //freopen("testdata.in","r",stdin);
80     n=read(),q=read();
81     for(int i=1;i<=n;++i){
82         val[i]=read(),pos[val[i]]=i;
83         a1[i]=ask(n)-ask(val[i]);
84         ans+=a1[i];
85         for(int j=val[i];j<=n;j+=lowbit(j)) ++c[j];
86     }
87     memset(c,0,sizeof(c));
88     for(int i=n;i;--i){
89         a2[i]=ask(val[i]-1);
90         for(int j=val[i];j<=n;j+=lowbit(j)) ++c[j];
91     }
92     while(q--){
93         printf("%lld\n",ans);
94         int x=read();x=pos[x];
95         ans-=(a1[x]+a2[x]-querysub(1,x-1,val[x])-querypre(x+1,n,val[x]));
96         for(int j=x;j<=n;j+=lowbit(j)) update(rt[j],1,n,val[x]);
97     }
98     return 0;
99 }
View Code

猜你喜欢

转载自www.cnblogs.com/bztMinamoto/p/9398329.html