表示蒟蒻只能想到O(sqrt(n)*n*logn)的算法,写完自测满n随机数据4.2秒交上去竟然过了。。。目测是只有一组数据时间限制40秒,宽松的不要不要的。。。
题意简述:
给出一个长度为n的序列,q个提问,每次提问[l,r]的区间内有多少对逆序对(q,n<=50000)
思路:
看到逆序对自然要想到树状数组来解了,但是树状数组求逆序对是通过每次扫描所求区间,时间效率O(nlogn),总时间复杂度O(q*n*logn)难以忍受
那么树状数组求逆序对是否可以动态呢,事实上可以,但是只能从当前已扫描的区间的两端插入一个元素,每次插入时间O(logn),最坏情况每次区间左右分别移动n,时间效率仍然O(q*n*logn)
考虑到区间的移动,又可以离线,就可以想到莫队这个区间离线神器了,莫队的核心思想是通过将提问排序尽量降低求值区间左右端点的移动距离。具体做法是将序列分块,先按照左端点所在的块的序号排序,再按照右端点位置排序。这样当左端点在每个块中时,右端点最多移动n次,右端点共移动O(n*sqrt(n));左端点在块中移动距离不超过O(sqrt(n)),共(q*sqrt(n)),这样时间效率就可以保证了
代码:
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 50005
#define maxq 50005
#define lowbit(x) ((x)&(-x))
int n,q,blocksize;
int a[maxn];
int ls[maxn];
int c[maxn];
int ans[maxq];
struct query
{
int l;
int r;
int id;
}qr[maxq];
inline bool operator <(const query a,const query b)
{
return (a.l/blocksize!=b.l/blocksize)?\
(a.l/blocksize<b.l/blocksize):a.r<b.r;
}
inline void add(int p,int v)
{
while(p<=n)
{
c[p]+=v;
p+=lowbit(p);
}
}
inline int getsum(int p)
{
int ans=0;
while(p)
{
ans+=c[p];
p-=lowbit(p);
}
return ans;
}
int main()
{
scanf("%d",&n);
blocksize=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ls[i]=a[i];
}
sort(ls+1,ls+n+1);
for(int i=1;i<=n;i++)
a[i]=lower_bound(ls+1,ls+n+1,a[i])-ls;
scanf("%d",&q);
for(int i=0;i<q;i++)
{
scanf("%d%d",&qr[i].l,&qr[i].r);
qr[i].id=i;
}
sort(qr,qr+q);
int l=qr[0].l;
int r=qr[0].r;
int tmp=0;
for(int i=r;i>=l;i--)
{
tmp+=getsum(a[i]);
add(a[i],1);
}
ans[qr[0].id]=tmp;
for(int i=1;i<q;i++)
{
while(r<qr[i].r)
{
r++;
tmp+=r-l-getsum(a[r]);
add(a[r],1);
}
while(r>qr[i].r)
{
add(a[r],-1);
tmp-=r-l-getsum(a[r]);
r--;
}
while(l>qr[i].l)
{
l--;
tmp+=getsum(a[l]);
add(a[l],1);
}
while(l<qr[i].l)
{
add(a[l],-1);
tmp-=getsum(a[l]);
l++;
}
ans[qr[i].id]=tmp;
}
for(int i=0;i<q;i++)
printf("%d\n",ans[i]);
}