【BZOJ】3295: [Cqoi2011]动态逆序对
【题意】给定1~n的排列,m次操作每次删除一个数,求每次操作前的逆序对数。n<=10^5,m<=50000。
【算法】CDQ分治+树状数组
【题解】先树状数组求逆序对,然后统计每次删除的逆序对数。
每个数字及其坐标构成一个二维坐标,逆序对就是两个相对位置在左上右下的点,那么删除一个点会减少的逆序对就是其左上偏序和右下偏序中的点数,转化为有操作顺序的二维平面矩阵查询和单点修改,用CDQ分治或树套树解决。
具体细节:
1.先加入所有加点操作(原数字必须作为最开始的操作进入CDQ分治过程),再加入前m-1个删除和询问。
2.第一次统计右下偏序,按x轴逆序排序后CDQ分治。
3.第二次统计左上偏序,令所有点y=n-y+1,然后按x轴正序排序后CDQ分治。
#include<cstdio> #include<cctype> #include<cstring> #include<algorithm> #define lowbit(x) (x&-x) using namespace std; int read(){ char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } const int maxn=100010; int n,m,ans[maxn],c[maxn],t[maxn],A[maxn],tot=0; struct cyc{int id,kind,qid,x,y;}a[maxn*2],b[maxn*2]; long long ANS=0; void modify(int x,int k){for(int i=x;i<=n;i+=lowbit(i))c[i]+=k;} int query(int x){int as=0;for(int i=x;i>=1;i-=lowbit(i))as+=c[i];return as;} bool cmp(cyc a,cyc b){return a.x<b.x||(a.x==b.x&&a.id<b.id);} bool cmp2(cyc a,cyc b){return a.x>b.x||(a.x==b.x&&a.id<b.id);} void CDQ(int l,int r){ if(l==r)return; int mid=(l+r)>>1; for(int i=l;i<=r;i++){ if(a[i].id<=mid&&!a[i].kind)modify(a[i].y,a[i].qid?-1:1); if(a[i].id>mid&&a[i].kind)ans[a[i].qid]+=query(a[i].y); } for(int i=l;i<=r;i++)if(a[i].id<=mid&&!a[i].kind)modify(a[i].y,a[i].qid?1:-1); int x1=l-1,x2=mid; for(int i=l;i<=r;i++)if(a[i].id<=mid)b[++x1]=a[i];else b[++x2]=a[i]; for(int i=l;i<=r;i++)a[i]=b[i]; CDQ(l,mid);CDQ(mid+1,r); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ A[i]=read(); t[A[i]]=i; modify(A[i],1); ANS+=i-query(A[i]); } if(m>1)printf("%lld\n",ANS); memset(c,0,sizeof(c)); for(int i=1;i<=n;i++)a[++tot]=(cyc){tot,0,0,i,A[i]}; for(int i=1;i<m;i++){ int x=read(); a[++tot]=(cyc){tot,0,i,t[x],x}; a[++tot]=(cyc){tot,1,i,t[x],x}; } sort(a+1,a+tot+1,cmp2); CDQ(1,tot); for(int i=1;i<=tot;i++)a[i].y=n-a[i].y+1; sort(a+1,a+tot+1,cmp); CDQ(1,tot); for(int i=1;i<m;i++){ ANS-=ans[i]; printf("%lld\n",ANS); } return 0; }