【BZOJ 3289】Mato的文件管理

题目描述

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

算法分析

最小交换次数为序列中的逆序对数,可以用树状数组维护。
离散化,然后利用莫队算法离线处理,区间变化时计算变化部分的贡献(在右边找比它大的就是 l e n ( l , r ) − q u e r y ( i d [ x ] ) len(l,r)-query(id[x]) len(l,r)query(id[x]),在左边找比它小的就是 q u e r y ( i d [ x ] − 1 ) query(id[x]-1) query(id[x]1)),在树状数组上修改,时间复杂度为 O ( n n l o g 2 n ) O(n\sqrt{n}log_2n) O(nn log2n),注意细节。

写完程序后发现跑得很慢,对比一下才发现自己之前的写法是有问题的。
原来的写法是:

for(register int i=0;i<q;++i) bl[i]=i/sz;

由于 bl[i] 反应的是区间左端点为 i i i 是所在块的编号,所以这段代码应该是:

for(register int i=1;i<=n;++i) bl[i]=(i-1)/sz;

改完之后跑得飞快。

代码实现

#include <cstdio>
#include <cmath>
#include <algorithm>
const int maxn=50005;
int a[maxn],h[maxn],id[maxn],hsz;
struct ask {int id,l,r;} asks[maxn];int sz,bl[maxn];
inline bool cmp(const ask &x,const ask &y) {return bl[x.l]^bl[y.l]?x.l<y.l:bl[x.l]&1?x.r<y.r:x.r>y.r;}
int bit[maxn],ans[maxn],sum=0;
inline void add(int x,int d) {for(;x<=hsz;x+=x&-x) bit[x]+=d;}
inline int query(int x) {int ans=0;for(;x;x-=x&-x) ans+=bit[x];return ans;}
int main() {
	int n;scanf("%d",&n);
	for(register int i=1;i<=n;++i) {scanf("%d",&a[i]);h[i-1]=a[i];}
	std::sort(h,h+n);hsz=std::unique(h,h+n)-h;
	int q;scanf("%d",&q);int sz=n/sqrt(q*2/3);for(register int i=1;i<=n;++i) bl[i]=(i-1)/sz;
	for(register int i=0;i<q;++i) {asks[i].id=i;scanf("%d%d",&asks[i].l,&asks[i].r);}
	std::sort(asks,asks+q,cmp);
	for(register int i=1;i<=n;++i) id[i]=std::lower_bound(h,h+hsz,a[i])-h+1;
	for(register int i=0,l=1,r=0;i<q;++i) {
		while(l<asks[i].l) {sum-=query(id[l]-1);add(id[l],-1);++l;}
		while(l>asks[i].l) {--l;sum+=query(id[l]-1);add(id[l],1);}
		while(r<asks[i].r) {++r;sum+=r-1-l+1-query(id[r]);add(id[r],1);}
		while(r>asks[i].r) {sum-=r-l+1-query(id[r]);add(id[r],-1);--r;}
		ans[asks[i].id]=sum;
	}
	for(register int i=0;i<q;++i) printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值