BZOJ3295 CQOI2011 动态逆序对
3295: [Cqoi2011]动态逆序对
Time Limit: 10 Sec Memory Limit: 128 MBDescription
对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。
给1到n的一个排列,按照某种顺序依次删除m个元素。
你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
Input
输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。
以下n行每行包含一个1到n之间的正整数,即初始排列。
以下m行每行一个正整数,依次为每次删除的元素。
Output
输出包含m行,依次为删除每个元素之前,逆序对的个数。
Sample Input
5 4
1 5 3 4 2
5 1 4 2
Sample Output
5
2
2
1
样例解释
(1,5,3,4,2) (1,3,4,2) (3,4,2) (3,2) (3)。
2
2
1
样例解释
(1,5,3,4,2) (1,3,4,2) (3,4,2) (3,2) (3)。
HINT
N<=100000 M<=50000
表示当时没看出来是CDQ,树套树倒是瞄出来了,只是不会写啊。
现在看来还是很显然的。
对于排列中的数,记它被删除的时间为t(未删除的设为m+1),下标为X,大小为Y。
那么对于一个数i,来看在它前面被删除的数对它的答案的减小值为多少:
Σ(Tj<Ti && Xj<Xi && Yj>Yi) + Σ(Tj<Ti && Xj>Xi && Yj<Yi)
做两次CDQ就好了。这种没有重复的算起来真是爽啊。
做的时候没有草稿纸真TM不爽。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <vector> #include <cstring> using namespace std; const int N = 100010; struct Data{ int X,Y,T;long long len; bool operator <(const Data &t)const{ return T<t.T; } }rem[N],f[N],t[N]; int n,m,ban[N],bplace[N],A[N],T[N]; long long Ans,ans[N]; inline int gi() { int x=0,res=1;char ch=getchar(); while(ch>'9' || ch<'0'){if(ch=='-')res=-res;ch=getchar();} while(ch>'/' && ch<':')x=x*10+ch-48,ch=getchar(); return x*res; } inline int lb(int k){return k&-k;} inline void update(int x){for(;x<=n;x+=lb(x))T[x]++;} inline int query(int x) { int ans=0; for(;x;x-=lb(x))ans+=T[x]; return ans; } inline void clean(int x){for(;x<=n;x+=lb(x))T[x]=0;} inline void calc() { memset(T,0,sizeof(T));Ans=0; for(int i=1;i<=n;++i){ Ans+=(i-query(A[i])-1); update(A[i]); } memset(T,0,sizeof(T)); } //在外面保证T有序,CDQ内保证X有序,统计Y值。 //T:时间 X:位置 Y:权值 //CDQ calc Ti<Tj Xi<Xj Y[i]<Y[j] inline void CDQ(int l,int r) { if(l==r)return;int mid=(l+r)>>1; CDQ(l,mid);CDQ(mid+1,r); int x=l,y=mid+1,z=l; while(x<=mid && y<=r) if(f[x].X<f[y].X)update(f[x].Y),t[z++]=f[x++]; else f[y].len+=query(f[y].Y),t[z++]=f[y++]; while(x<=mid)t[z++]=f[x++]; while(y<=r)f[y].len+=query(f[y].Y),t[z++]=f[y++]; for(int i=l;i<=mid;++i)clean(f[i].Y); for(int i=l;i<=r;++i)f[i]=t[i]; } int main() { n=gi();m=gi(); for(int i=1;i<=n;++i) A[i]=gi(); for(int i=1;i<=m;++i) bplace[ban[i]=gi()]=i; for(int i=1;i<=n;++i){ rem[i].T=bplace[A[i]]; rem[i].X=i; rem[i].Y=A[i]; if(!rem[i].T)rem[i].T=m+1; } sort(rem+1,rem+n+1);calc(); for(int i=0;i<=m;++i)ans[i]=Ans; for(int i=1;i<=n;++i){ f[i]=rem[i]; f[i].Y=n-f[i].Y+1; } reverse(f+1,f+n+1); CDQ(1,n);sort(f+1,f+n+1); for(long long i=1,s=0;i<=n;++i){ if(!f[i].T)continue; ans[f[i].T]-=s;s+=f[i].len; } for(int i=1;i<=n;++i){ f[i]=rem[i]; f[i].X=n-f[i].X+1; } reverse(f+1,f+n+1); CDQ(1,n);sort(f+1,f+n+1); for(long long i=1,s=0;i<=n;++i){ if(!f[i].T)continue; ans[f[i].T]-=s;s+=f[i].len; } for(int i=1;i<=m;++i) printf("%lld\n",ans[i]); return 0; }
然后看见我在上古时期的分块??!!
一脸懵逼。
瞪了好久发现 不是我写的。
大概思想就是分块,每块内排序。
然后查询:
不在自己块里的,二分一下。
在自己块里的,暴力搞搞。
然后删掉。
6000ms...过去了...
昂波利污波。
#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-sort int 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; }