洛谷P3157/BZOJ3295[CQOI2011]动态逆序对(分块)
分块是个好算法
尤其对于我这种不会树套树的傻子
删除一个数时,不难发现它对于逆序对的贡献是它前面比它大的数的个数+它后面比它小的数的个数,于是用分块就非常容易了:
1. 算贡献,该数前面的块和后面的块二分,它所在的块暴力;
2. 把它删除,原数列打标记,块内删除后把后面的数往前移。
这样写的复杂度是$O(m\sqrt{n}logn)$的,相当危险,但是我们发现把块大小取成$\sqrt{nlogn}$的话,复杂度就降到了$O(m\sqrt{nlogn})$,不用卡常都可以轻松AC。
#include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N=100050; char rB[1<<21],*S,*T,wB[1<<21]; int wp=-1; inline char gc(){return S==T&&(T=(S=rB)+fread(rB,1,1<<21,stdin),S==T)?EOF:*S++;} inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;} inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;} inline int rd(){ char c=gc(); while(c<48||c>57)c=gc(); int x=c&15; for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15); return x; } short buf[25]; inline void wt(ll x){ short l=-1; while(x>9){ buf[++l]=x%10; x/=10; } pc(x|48); while(l>=0)pc(buf[l--]|48); pc('\n'); } //快读/快写 int a[N],b[100][1300],c[N],t[N],n,x,sz[100],K,cnt,pos[N]; ll ans=0; bool d[N]; void ms(int L,int R){ //归并排序求初始逆序对数 if(L==R)return; int M=L+R>>1,i=L,j=M+1,k=L; ms(L,M);ms(M+1,R); while(i<=M&&j<=R)if(c[i]<=c[j])t[k++]=c[i++]; else{ ans+=M-i+1; t[k++]=c[j++]; } while(i<=M)t[k++]=c[i++]; while(j<=R)t[k++]=c[j++]; for(i=L;i<=R;i++)c[i]=t[i]; } inline void build(){ int i,j; K=sqrt(n*log(n)/log(2));if(!K)K=1;cnt=n/K; //1*log(1)=0,碰上毒瘤数据(这题没有)会RE for(i=1;i<=cnt;i++){ for(j=1;j<=K;j++)b[i][j]=a[(i-1)*K+j]; sort(b[i]+1,b[i]+K+1); sz[i]=K; } if(n%K){ cnt++; for(i=1;i<=n%K;i++)b[cnt][i]=a[(cnt-1)*K+i]; sort(b[cnt]+1,b[cnt]+n%K+1); sz[cnt]=n%K; } } inline void inupper(int p,int x){ //块内二分更大值的个数 int l=1,r=sz[p],m,s=sz[p]+1; while(l<=r){ m=l+r>>1; if(b[p][m]>x){s=m;r=m-1;} else l=m+1; } ans-=sz[p]-s+1; } inline void inlower(int p,int x){ //块内二分更小值的个数 int l=1,r=sz[p],m,s=0; while(l<=r){ m=l+r>>1; if(b[p][m]<x){s=m;l=m+1;} else r=m-1; } ans-=s; } inline int find(int p,int x){ //暴力搜 int l=1,r=sz[p],m; while(l<r){ m=l+r>>1; if(b[p][m]>=x)r=m; else l=m+1; } return l; } inline void work(){ int p=(x+K-1)/K,i,t=find(p,a[x]); for(i=1;i<p;i++)inupper(i,a[x]); for(i=p+1;i<=cnt;i++)inlower(i,a[x]); for(i=(p-1)*K+1;i<x;i++)if(!d[i]&&a[i]>a[x])ans--; for(i=x+1;i<=p*K&&i<=n;i++)if(!d[i]&&a[i]<a[x])ans--; d[x]=1; //删除标记 for(i=t;i<sz[p];i++)b[p][i]=b[p][i+1]; //删除一个数后后面的数要前移 sz[p]--; //此块内元素减少 } int main(){ int m,i; n=rd();m=rd()-1; //既然最后一个不用输出答案,那就不用处理了 for(i=1;i<=n;i++)pos[a[i]=rd()]=i; //题目要删除的是数,所以要记下位置 build(); memcpy(c,a,sizeof(c)); ms(1,n); wt(ans); while(m--){ x=pos[rd()]; work(); wt(ans); } flush(); return 0; }
然后我就一直学不会树套树