您的位置:首页 > 其它

【BZOJ3289】【莫队分块+树状数组求逆序对】Mato的文件管理

2015-10-29 10:42 453 查看

3289: Mato的文件管理

Time Limit: 40 Sec  Memory Limit: 128 MB
Submit: 1123  Solved: 495

[Submit][Status][Discuss]

Description

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

Input

第一行一个正整数n,表示Mato的资料份数。

第二行由空格隔开的n个正整数,第i个表示编号为i的资料的大小。

第三行一个正整数q,表示Mato会看几天资料。

之后q行每行两个正整数l、r,表示Mato这天看[l,r]区间的文件。

Output

q行,每行一个正整数,表示Mato这天需要交换的次数。

Sample Input

4

1 4 2 3

2

1 2

2 4

Sample Output

0

2

HINT

Hint

n,q <= 50000

样例解释:第一天,Mato不需要交换

第二天,Mato可以把2号交换2次移到最后。

Source

By taorunz

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<iostream>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
const int N=5e4+10,M=0,Z=1e9+7,ms63=1061109567;
int casenum,casei;
int v
,w
;
map<int,int>mop;
map<int,int>::iterator it;
int n,m;
struct A
{
int l,r,o,id;
bool operator < (const A& b)const
{
return id!=b.id?id<b.id:r<b.r;
}
}a
;
int b
,top,l,r;
void add(int x,int v)
{
for(;x<=top;x+=x&-x)b[x]+=v;
}
int cnt(int x)
{
int tmp=0;
for(;x;x-=x&-x)tmp+=b[x];
return tmp;
}
int Lins(int x)//每次在最前添加一个数,比这个数小的数都会与其形成一个逆序对
{
add(x,1);
return cnt(x-1);
}
int Rins(int x)//每次在最后添加一个数,比这个数大的数都会与其形成一个逆序对
{
add(x,1);
return r-l+1-cnt(x);
}
int Ldel(int x)//每次在最前移除一个数,比这个数小的数都会与其减少一个逆序对
{
add(x,-1);
return cnt(x-1);
}
int Rdel(int x)//每次在最后移除一个数,比这个数大的数都会与其减少一个逆序对
{
add(x,-1);
return r-l+1-cnt(x);
}
int ans
;
void reorder()//采取两种方式做离散化处理,效率差别并不大
{
mop.clear();
for(int i=1;i<=n;i++)read(v[i]),mop[v[i]]=0;
for(top=0,it=mop.begin();it!=mop.end();it++)it->second=++top;
for(int i=1;i<=n;i++)v[i]=mop[v[i]];

/*for(int i=1;i<=n;i++)read(v[i]),w[i]=v[i];
sort(w+1,w+n+1);
for(int i=1;i<=n;i++)v[i]=lower_bound(w+1,w+n+1,v[i])-w;
top=n;*/
}
int main()
{
//fre();
while(read(n))
{
reorder();
int len=sqrt(n);
read(m);
for(int i=1;i<=m;i++)
{
read(a[i].l);read(a[i].r);
a[i].o=i;
a[i].id=a[i].l/len;
}
sort(a+1,a+m+1);
l=a[1].l;
r=l-1;
int ANS=0;
MS(b,0);
for(int i=1;i<=m;i++)
{
while(l>a[i].l)ANS+=Lins(v[--l]);//左界左移
while(r<a[i].r)ANS+=Rins(v[++r]);//右界右移
while(l<a[i].l)ANS-=Ldel(v[l++]);//左界右移
while(r>a[i].r)ANS-=Rdel(v[r--]);//右界左移
ans[a[i].o]=ANS;
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}
return 0;
}
/*
【trick&&吐槽】
1,莫队指针移动,对于答案的贡献的±号千万不能写反了
2,排序规则不要写错QwQ
3,对于while(l<a[i].l)ANS-=Ldel(v[l++]);这样的代码,
传进的参数是Ldel(v[l+1]),但是在过程内部,l已经变成了l+1。
也就是说,l++发生在过程执行之前,而并非是执行完这个过程之后。

【题意】
有n(5e4)个数,有m(5e4)个区间询问。
对于每个询问[l,r],我们想要知道在[l,r]范围内有多少个逆序对。

【类型】
莫队分块+树状数组

【分析】
1,对于逆序对的询问,可以把权值按照大小做离散化,然后——
每次在最后添加一个数,比这个数大的数都会与其形成一个逆序对
每次在最后移除一个数,比这个数大的数都会与其形成一个逆序对
每次在最前添加一个数,比这个数小的数都会与其减少一个逆序对
每次在最前移除一个数,比这个数小的数都会与其减少一个逆序对

2,这题可以把区间询问离线处理,于是可以通过莫队实现

【时间复杂度&&优化】
O(n^1.5*log(n))
cnt(top)这样的命令可以省略为——直接求出当前的数字总数,这个会加快一定的速度。

【数据】
Sample Input
4
1 4 2 3
2
1 2
2 4

Sample Output
0
2

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: