Description

Mato同学从各路神犇以各种方式(你们懂的)收集了许多资料,这些资料一共有n份,每份有一个大小和一个编号。为了防止他人偷拷,这些资料都是加密过的,只能用Mato自己写的程序才能访问。Mato每天随机选一个区间[l,r],他今天就看编号在此区间内的这些资料。Mato有一个习惯,他总是从文件大小从小到大看资料。他先把要看的文件按编号顺序依次拷贝出来,再用他写的排序程序给文件大小排序。排序程序可以在1单位时间内交换2个相邻的文件(因为加密需要,不能随机访问)。Mato想要使文件交换次数最小,你能告诉他每天需要交换多少次吗?
题面是直接复制下来的,对题意有什么问题就直接问。

Solution

莫队算法

一看到这道题目,我可以从[l,r]区间用O(1)的时间分别向左向右扩展一个,符合莫队的条件。

逆序对怎么办

要求最小次数变成有序的情况肯定是求逆序对的次数。
逆序对怎么求。
既然不要求修改,那么求逆序对肯定是用树状数组来求了。
用树状数组只用h来统计当前有多少个比你小的,那么前面有多少个比你大的就用序列的总长度来减去有多少个比你小的就可以了。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
typedef long long ll;
using namespace std;
const int maxn=50007;
int i,j,k,l,t,n,m,ans[maxn],ans1,r,a[maxn],kuai,h[maxn];
struct node{
int a,b,c,d,e;
}b[maxn];
bool cmp(node x,node y){
return x.d<y.d||x.d==y.d&&x.e<y.e;
}
int lowbit(int x){
return (-x)&x;
}
void add(int x,int z){
int i,j,k;
while(x<=n){
h[x]+=z;
x+=lowbit(x);
}
}
int find(int x){
int i,j,k=0;
while(x>0){
k+=h[x];
x-=lowbit(x);
}
return k;
}
int main(){
freopen("file.in","r",stdin);
freopen("file.out","w",stdout);
scanf("%d",&n);kuai=sqrt(n);
fo(i,1,n)scanf("%d",&a[i]);
scanf("%d",&m);
fo(i,1,m){
scanf("%d%d",&b[i].a,&b[i].b);
b[i].c=i;b[i].d=b[i].a/kuai+1;b[i].e=b[i].b/kuai+1;
}
sort(b+1,b+1+m,cmp);
l=1,r=0;
fo(i,1,m){
while(l<b[i].a)add(a[l],-1),ans1-=find(a[l]-1),l++;
while(l>b[i].a)l--,add(a[l],1),ans1+=find(a[l]-1);
while(r<b[i].b)r++,add(a[r],1),ans1+=r-l+1-find(a[r]);
while(r>b[i].b)add(a[r],-1),ans1-=r-l-find(a[r]),r--;
ans[b[i].c]=ans1;
}
fo(i,1,m)printf("%d\n",ans[i]);
}