bzoj3295: [Cqoi2011]动态逆序对(树套树)

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <algorithm>
 6 #define maxn 100005
 7 #define maxk 6000005
 8 using namespace std;
 9 
10 int n,m,size,num[maxn],pos[maxn],tsum[maxn],t1[maxn],t2[maxn];
11 typedef long long ll;
12 ll ans;
13 int sum[maxk],lc[maxk],rc[maxk],root[maxn];
14 
15 int lowbit(int x){
16     return x&(-x);
17 }
18 
19 void tree_insert(int x){
20     for (int i=x;i<=n;i+=lowbit(i)) tsum[i]++;
21 }
22 
23 int tree_sum(int x){
24     int temp=0;
25     for (int i=x;i>0;i-=lowbit(i)) temp+=tsum[i];
26     return temp;
27 }
28 
29 int query_sum(int k,int l,int r,int x,int y){
30     if (!k) return 0;
31     if (l>=x&&r<=y){
32         return sum[k];
33     }
34     int mid=(l+r)/2,temp=0;
35     if (x<=mid) temp+=query_sum(lc[k],l,mid,x,y);
36     if (y>mid) temp+=query_sum(rc[k],mid+1,r,x,y);
37     return temp;
38 }
39 
40 int query(int lim,int l,int r){
41     int temp=0;
42     for (int i=lim;i>0;i-=lowbit(i)){
43         temp+=query_sum(root[i],1,n,l,r);
44     }
45     return temp;
46 }
47 
48 void update(int &k,int l,int r,int x){
49     if (!k) k=++size;
50     sum[k]++;
51     if (l==r) return;
52     int mid=(l+r)/2;
53     if (x<=mid) update(lc[k],l,mid,x);
54     else update(rc[k],mid+1,r,x);
55 }
56 
57 void insert(int lim,int x){
58     for (int i=lim;i<=n;i+=lowbit(i)){
59         update(root[i],1,n,x);
60     }
61 }
62 
63 int main(){
64 //    freopen("dtnxd.in","r",stdin);
65 //    freopen("dtnxd.out","w",stdout);
66     scanf("%d%d",&n,&m),ans=size=0;
67     memset(sum,0,sizeof(sum));
68     memset(root,0,sizeof(root));
69     memset(tsum,0,sizeof(tsum));
70     for (int i=1;i<=n;i++){
71         scanf("%d",&num[i]),pos[num[i]]=i;
72         t1[i]=tree_sum(n)-tree_sum(num[i]);
73         ans+=t1[i];
74         tree_insert(num[i]);
75     }
76     memset(tsum,0,sizeof(tsum));
77     for (int i=n;i>=1;i--){
78         t2[i]=tree_sum(num[i]-1);
79         tree_insert(num[i]);
80     }
81     memset(tsum,0,sizeof(tsum));
82 //    for (int i=1;i<=n;i++) printf("%d %d
",t1[i],t2[i]);
83     int u,v;
84     for (int i=1;i<=m;i++){
85         scanf("%d",&u),v=pos[u];
86         printf("%lld
",ans);
87         ans-=(t1[v]+t2[v]);
88         ans+=query(v-1,u+1,n);
89         ans+=query(n,1,u-1);
90         ans-=query(v,1,u-1);
91         insert(v,u);
92     }
93     return 0;
94 }
View Code

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3295

题目大意:对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

做法:一种暴力写法,就是每次删除前,重新用树状数组维护逆序对,复杂度mnlogn,是接受不了的。我们考虑每次删除一个数后对ans的影响,其影响就是ans-=前面未被删除的数中比它大的个数+后面未被删除的数中比它小的数的个数。这是一个经典的问题:询问一段区间l~r中权值在x~y范围内的个数,支持修改操作,我们能想到的便是树状数组套动态开点的权值线段树(可持久化线段树),但是恶心的是这题卡空间,我们可以这样优化一下:

我们用一维树状数组即可预处理出两个数组,t1[],t2[],分别表示初始序列中在i之前的比它大的个数、初始序列中在i之后的比它小的个数,我们就不需要把初始的n个数也加进树套树中了,我们删除一个数,ans-=(t1+t2-在它之前的已被删除的比它大的个数-在它之后已被删除的比它小的个数),我们便只需要将m个数加入到树套树中了,空间复杂度优化了不少,mlogn^2,不会MLE。

还有一种cdq分治的写法,用的归并排序的思想,以后再写吧。

树套树。

原文地址:https://www.cnblogs.com/OYzx/p/5530337.html