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

这是一个关于BZOJ3289题目的博客,讨论了如何使用莫队分块和树状数组的方法来求解Mato在管理文件过程中遇到的逆序对问题。题目要求解决在给定资料大小和查看天数的情况下,Mato每天需要交换文件的次数。
摘要由CSDN通过智能技术生成

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

#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[N],w[N];
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[N];
int b[N],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[N];
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

*/


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值