首页 > 代码库 > 主席树初探 & bzoj 3295: [Cqoi2011] 动态逆序对 题解
主席树初探 & bzoj 3295: [Cqoi2011] 动态逆序对 题解
【原题】
3295: [Cqoi2011]动态逆序对
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 778 Solved: 263
[Submit][Status]
Description
Input
Output
Sample Input
1
5
3
4
2
5
1
4
2
Sample Output
2
2
1
样例解释
(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。
HINT
N<=100000 M<=50000
【题外话】一直想A了这道题。真累啊!开始的想法是——倒着做,一个一个添加,套一个树状数组,然后每次用平衡树去寻找。显然,现在我只会SPLAY,而且代码长,效率渣。今天和SYF大神一起看hzwer大神的博客,加之哲大爷的一语道破,总算会了树状数组套主席树的方法。
【主要思路】首先用树状数组/归并排序/归并树(时代的眼泪)求出总的序列中的逆序对个数。每次删掉的时候,用树状数组维护位置(1--x-1或x+1--n),并且用权值线段树维护权值信息。
【详细算法】
①在之前的计算总的逆序对个数的时候,我们可以顺带的算出front[i]和back[i],表示第i个点之前有front[i]个数比他大,之后有back[i]个数比他小。
②起初刚开始做的时候,主席树内部是空的。因此我们要转化——我们本来计算的是删掉某个数后,将会减少所剩的数列中的多少的逆序对(即对所剩的序列产生多少的贡献);现在我们改成:删掉某个数后,将会对主席树中已经插入的一些节点产生多少的贡献。这样算出来后,用front或back减一下即可。
举个例子。比如有数列5,4,3,2,1,已经删掉了4和1,ans值是3。现在我们要删掉3了。在主席树中,已添加的节点是5和2。通过计算,5,3,2会在3的前面产生一个逆序对,在3的后面产生一个逆序对。而front[3]=2,back[3]=2,那么我们可以知道,ans需要减去front[3]-1与back[3]-1,即最后ans只剩下了1。
③后来套的树状数组具体是怎么实现的呢?比如我们要计算或更新l~r范围内主席树中的某个或某些权值的个数。我们可以转化为计算或更新1--l-1与1--r的前缀,再相减即可。而前缀是可以用树状数组解决的。
【代码】
#include<cstdio> #include<cstring> #define N 100005 #define L(x) (x&-x) using namespace std; typedef long long LL;LL ans; struct arr{int l,r;LL sum;}a[6000000]; int front[N],back[N],c[N],root[N],pos[N],data[N],A[31],B[31]; int n,node,del,x,m,i; inline int ask(int x){int s=0;for (;x;x-=L(x)) s+=c[x];return s;} inline void up(int x){for (;x<=n;x+=L(x)) c[x]++;} inline LL ask_more(int l,int r,int num) { if (l>r) return 0;l--;A[0]=B[0]=0; for (int i=l;i;i-=L(i)) A[++A[0]]=root[i]; for (int i=r;i;i-=L(i)) B[++B[0]]=root[i]; l=1;r=n;LL sum=0; while (l!=r) { int mid=(l+r)>>1; if (num<=mid) { for (int i=1;i<=B[0];i++) sum+=a[a[B[i]].r].sum; for (int i=1;i<=A[0];i++) sum-=a[a[A[i]].r].sum; for (int i=1;i<=B[0];i++) B[i]=a[B[i]].l; for (int i=1;i<=A[0];i++) A[i]=a[A[i]].l; r=mid; } else { for (int i=1;i<=B[0];i++) B[i]=a[B[i]].r; for (int i=1;i<=A[0];i++) A[i]=a[A[i]].r; l=mid+1; } } return sum; } inline LL ask_less(int l,int r,int num) { if (l>r) return 0;l--;A[0]=B[0]=0; for (int i=l;i;i-=L(i)) A[++A[0]]=root[i]; for (int i=r;i;i-=L(i)) B[++B[0]]=root[i]; l=1;r=n;LL sum=0; while (l!=r) { int mid=(l+r)>>1; if (num>mid) { for (int i=1;i<=B[0];i++) sum+=a[a[B[i]].l].sum; for (int i=1;i<=A[0];i++) sum-=a[a[A[i]].l].sum; for (int i=1;i<=B[0];i++) B[i]=a[B[i]].r; for (int i=1;i<=A[0];i++) A[i]=a[A[i]].r; l=mid+1; } else { for (int i=1;i<=B[0];i++) B[i]=a[B[i]].l; for (int i=1;i<=A[0];i++) A[i]=a[A[i]].l; r=mid; } } return sum; } inline void update(int &k,int l,int r) { if (!k) k=++node;a[k].sum++; if (l==r) return; int mid=(l+r)>>1; if (del<=mid) update(a[k].l,l,mid); else update(a[k].r,mid+1,r); } inline int Read() { char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar()); int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; return x; } int main() { n=Read();m=Read(); for (i=1;i<=n;i++) { data[i]=Read();pos[data[i]]=i; ans+=(front[i]=i-1-ask(data[i]));up(data[i]); } memset(c,0,sizeof(c)); for (i=n;i;i--) back[i]=ask(data[i]-1),up(data[i]); for (i=1;i<m;i++) { printf("%lld\n",ans); del=Read();x=pos[del]; ans-=front[x]-ask_more(1,x-1,del); ans-=back[x]-ask_less(x+1,n,del); for (;x<=n;x+=L(x)) update(root[x],1,n); } printf("%lld\n",ans); return 0; }