[BZOJ 3295] CQOI 2011 动态逆序对 · 分块 & 逆序对

来源:互联网 发布:淘宝开茶叶店 编辑:程序博客网 时间:2024/05/17 08:55

最优算法CDQ分治或树套树,并不会做。

分块大法好!

要求每次删除前的逆序对数,可以转化成求每次删除一个数后的逆序对数。

删除之前的对数,减去与当前要删除的数相关的逆序对数,就是每次的答案。


我们可以把原序列a[]分成个块,每个块有个数。然后维护一个b[],b[]中的每一个块都是一个单调递增的序列。

当前要删除的数为x,所在的块是k,那我们分两种情况做:
1.对于k号块,直接在a[]的k号块中枚举求逆序对,复杂度为

2.对于除了k号块以外的每一个块,利用b[]二分求x在当前块中产生的逆序对数,复杂度为gif.latex (88×19)

然后删除这个数,把b[]中相对应的数变为0,把这个块重新排序。


因为我们要保证每个块的大小不变,并且每个数所在的块的编号也不变,所以我们在删除的时候不能直接删除。

既然不能直接删除,我们就得搞一些东西来维护。

在计算第一步的时候,我们开一个bool数组f[i],表示i这个数有没有被删掉,然后枚举的时候判断一下。

在计算第二步的时候,我们开一个数组sum[],sum[i]表示第i个块里删除了多少个数。这样的话,如果我们在计算的时候这个块里产生了t对逆序对,而已经被删除的数都变成0,所以每个0都会产生一个逆序对,那么不算上已经被删除的实际只有t-sum[i]个逆序对。


具体细节见注释。

#include <cstdio>#include <cmath>#include <cstdlib>#include <algorithm>#include <cstring>using namespace std;#define ll long long const int N=200050;int n,m,a[N];int tmp[N],t[N];//merge-sortint belong[N],L[N],R[N],cnt,k,x,y;int ad[N];      //i在原数列中的位置为ad[i]  int b[N];       //每个块维护有序数列 int sum[N];     //当前块中删除了多少个数 int adx;int i;bool f[N];ll tot;        //当前逆序对数   开 long long ! inline int get(){         int p=0;char x=getchar();         while (x<'0' || x>'9') x=getchar();         while (x>='0' && x<='9') p=p*10+x-'0',x=getchar();         return p;     } inline void merge_sort(int l,int r){         int mid,p,i,j;         mid=(l+r)>>1;    i=p=l;j=mid+1;         while (i<=mid && j<=r)        if (tmp[i]>tmp[j]) t[p++]=tmp[j++],tot+=mid-i+1;            else t[p++]=tmp[i++];         while (i<=mid) t[p++]=tmp[i++];         while (j<=r) t[p++]=tmp[j++];         for (i=l;i<=r;i++) tmp[i]=t[i];         return ;} inline void merge(int l,int r){         if (l>=r) return ;         int mid=(l+r)>>1;         merge(l,mid);         merge(mid+1,r);         merge_sort(l,r);         return ;} void init(){         n=get();m=get();         k=sqrt(n);      //块大小          cnt=n/k;if (n%k) cnt++; //块个数          for (int i=1;i<=n;i++)        a[i]=get(),ad[a[i]]=i,belong[i]=(i-1)/k+1;         for (int i=1;i<=cnt;i++)        L[i]=i*k-k+1,R[i]=i*k;    R[cnt]=n;         memcpy(tmp,a,sizeof tmp);         tot=0;         merge(1,n);         memcpy(b,a,sizeof a);         for (int i=1;i<=cnt;i++)        sort(b+L[i],b+R[i]+1);         memset(f,1,sizeof f);         return ;} inline int search(int t,int p){    int l,r,ret;         l=L[t]; r=R[t]; ret=R[t];         while (l<=r){                 int mid=(l+r)>>1;                 if (b[mid]<=p) ret=mid,l=mid+1;                         else r=mid-1;    }         if (b[ret]>p) ret=L[i]-1;         return ret;} int main(){     init();         for (int p=1;p<=m;p++){                 printf("%lld\n",tot);                 y=get();x=ad[y];        //得到在a数列中的位置 所处的块的编号肯定不变                 k=belong[x];            //x属于第k个块                  for (i=1;i<k;i++)            tot-=R[i]-search(i,a[x]);                 for (i=k+1;i<=cnt;i++)            tot-=search(i,a[x])-L[i]+1-sum[i];                     for (i=L[k];i<x;i++)        if (f[a[i]] && a[i]>a[x]) tot--;                for (i=x+1;i<=R[k];i++)        if (f[a[i]] && a[i]<a[x]) tot--;                 adx=search(k,a[x]);b[adx]=0;sum[k]++;f[a[x]]=0;                 sort(b+L[k],b+R[k]+1);        }         return 0;}


2.对于k号块,直接在a[]的k号块中枚举求逆序对,复杂度为
0 0