来自FallDream的博客,未经允许,请勿转载,谢谢。
Mato同学从各路神犇以各种方式(你们懂的)收集了许多资料,这些资料一共有n份,每份有一个大小和一个编号。为了防止他人偷拷,这些资料都是加密过的,只能用Mato自己写的程序才能访问。Mato每天随机选一个区间[l,r],他今天就看编号在此区间内的这些资料。Mato有一个习惯,他总是从文件大小从小到大看资料。他先把要看的文件按编号顺序依次拷贝出来,再用他写的排序程序给文件大小排序。排序程序可以在1单位时间内交换2个相邻的文件(因为加密需要,不能随机访问)。Mato想要使文件交换次数最小,你能告诉他每天需要交换多少次吗? n<=50000
求的是区间的逆序对数量... 所以直接莫队+线段树转移就行了。复杂度$O(n^{frac{3}{2}}logn)$
当然还可以分块,选择根号个点,处理出每个点到所有点的答案,然后每次询问暴力跳 上个主席树查逆序对数量就行了 复杂度一样 但是支持在线
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #define MN 50000 #define N 65534 #define ll long long using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int n,s[MN+5],m,cnt=1,size,block[MN+5],T[N*2+5],L,R,l[MN+5]; struct ques{int l,r,id;}q[MN+5]; bool cmp(ques x,ques y){return block[x.l]==block[y.l]?x.r<y.r:x.l<y.l;} ll ans=0,Ans[MN+5]; inline int query(int l,int r) { if(l>r) return 0;int sum=0; for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) { if(~l&1) sum+=T[l+1]; if( r&1) sum+=T[r-1]; } return sum; } void renew(int x,int ad) { T[x+=N]+=ad; for(x>>=1;x;x>>=1) T[x]=T[x<<1]+T[x<<1|1]; } inline void insL(int x,int ad) { ans+=ad*query(1,s[x]-1); renew(s[x],ad); } inline void insR(int x,int ad) { ans+=ad*query(s[x]+1,cnt); renew(s[x],ad); } int main() { n=read();size=sqrt(n); for(int i=1;i<=n;++i) block[i]=(i-1)/size+1; for(int i=1;i<=n;++i) s[i]=l[i]=read(); sort(l+1,l+n+1); for(int i=2;i<=n;++i) if(s[i]!=s[i-1]) s[++cnt]=s[i]; for(int i=1;i<=n;++i) s[i]=lower_bound(l+1,l+cnt+1,s[i])-l; m=read(); for(int i=1;i<=m;++i) q[i].l=read(),q[i].r=read(),q[i].id=i; sort(q+1,q+m+1,cmp); for(int i=1,last=0;i<=m;++i) { if(block[q[i].l]!=last) { memset(T,0,sizeof(T)); insL(L=R=(block[q[i].l]-1)*size+1,1); last=block[q[i].l];ans=0; } while(L<q[i].l) insL(L++,-1); while(L>q[i].l) insL(--L, 1); while(R<q[i].r) insR(++R, 1); Ans[q[i].id]=ans; } for(int i=1;i<=m;++i) printf("%lld ",Ans[i]); return 0; }