BZOJ#3295. [Cqoi2011]动态逆序对
3295: [Cqoi2011]动态逆序对
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 6672 Solved: 2350
Description
对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删
除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数
Input
输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。
以下n行每行包含一个1到n之间的正整数,即初始排列。
以下m行每行一个正整数,依次为每次删除的元素。
N<=100000 M<=50000
Output
输出包含m行,依次为删除每个元素之前,逆序对的个数。
Sample Input
5 4
1
5
3
4
2
5
1
4
2
1
5
3
4
2
5
1
4
2
Sample Output
5
2
2
1
2
2
1
题目大意:
给定一段排列,每次删掉一个数,求每次删掉这个数之前有多少对逆序对
分析:
可以用主席树,这次主要讲CDQ分治
很容易看出来是三维偏序
时间,位置,权值
然后很套路
我们先按时间排序
回溯的时候按位置排序
现在位置是有序的,但是时间和权值无序
我们统计过区间的逆序对
因为只考虑前面对后面的影响
所以只统计d[i].id>mid 的答案
树状数组先统计在它之前比它大的数
清空树状数组
再统计它后面比它小的数
清空树状数组
统计答案时
因为我们是从后往前统计的
按时间从小到大先累加,然后从n到n-m输出答案
附上代码:
#include <bits/stdc++.h> #define mid (l+r>>1) #define ll long long using namespace std; ll n,ret,m,tem; ll tr[200001],a[200001],in[200001]; bool ok[200001]; struct qu { ll num,pos,id,ans; } q[200001],t[200001]; bool com(qu a,qu b) { return a.id<b.id; } void add(ll x,ll y){ for(;x<=n;x+=x&-x) tr[x]+=y;} ll que(ll x){ for(ret=0;x;x-=x&-x) ret+=tr[x];return ret;} void cdq(ll l,ll r) { if(l==r) return; cdq(l,mid);cdq(mid+1,r); for(ll i=l,j=l,k=mid+1;i<=r;i++) if((k>r) || (j<=mid && q[j].pos<q[k].pos)) t[i]=q[j++]; else t[i]=q[k++]; ll ALL=0; for(ll i=l;i<=r;i++) if((q[i]=t[i]).id>mid) q[i].ans+=ALL-que(q[i].num); else add(q[i].num,1),++ALL; for(ll i=l;i<=r;i++) if(q[i].id<=mid) add(q[i].num,-1); for(ll i=r;i>=l;i--) if(q[i].id>mid) q[i].ans+=que(q[i].num); else add(q[i].num,1); for(ll i=l;i<=r;i++) if(q[i].id<=mid) add(q[i].num,-1); } int main() { freopen("a.in","r",stdin); scanf("%d%d",&n,&m); for(ll i=1;i<=n;i++) scanf("%d",&tem),a[tem]=i; for(ll i=1;i<=m;i++) scanf("%d",&in[i]),ok[in[i]]=1; tem=0; for(ll i=1;i<=n;i++) if(!ok[i]) q[++tem].num=i,q[tem].pos=a[q[tem].num],q[tem].id=tem; for(ll i=m;tem<=n;i--) q[++tem].num=in[i],q[tem].pos=a[q[tem].num],q[tem].id=tem; cdq(1,n); sort(q+1,q+n+1,com); for(ll i=2;i<=n;i++) q[i].ans+=q[i-1].ans; for(ll i=n;i>n-m;i--) printf("%lld\n",q[i].ans); return 0; }