BZOJ P4240 有趣的家庭菜园 【树状数组】

题目分析:

首先有一个很常用并且十分显然的结论(楼楼不知道怎么证明):一串数交换后形成的以原来编号为元素的序列的逆序对数为最小交换次数,其中交换为两两交换(其实这个结论都比较能感觉到它的正确性)。

对于这道题,我们显然可以按照高度从小到大排序后枚举每个数,那么对于每个数它对答案的贡献就是剩下的数与它能够形成逆序对数的个数。

这个时候我们就可以根据题意开两个数组,其中一个维护比一个数的位置大的数的个数,另一个则反之。由于可能存在相同的数,我们在枚举的时候直接删掉就可以了。查询时我们只需要取两个当中的最小值,查询完后删除。

参考代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define DB double
#define SG string
#define LL long long
#define LowBit(X) (X&(-X))
#define Fp(A,B,C,D) for(A=B;A<=C;A+=D)
#define Fm(A,B,C,D) for(A=B;A>=C;A-=D)
#define Clear(A) memset(A,0,sizeof(A))
#define Copy(A,B) memcpy(A,B,sizeof(A))
using namespace std;
const LL Max=3e5+5;
const LL Mod=1e9+7;
const LL Inf=1e18;
struct Node{
	LL ID,Height;
	Node(){}
	Node(LL X,LL Y):ID(X),Height(Y){}
	void InPut(LL X,LL Y){
		ID=X,Height=Y;
	}
	friend bool operator < (const Node X,const Node Y){
		return X.Height<Y.Height;
	}
}Tree[Max];
LL N,Ans,A[Max],B[Max];
inline LL Read(){
	LL X=0;char CH=getchar();bool F=0;
	while(CH>'9'||CH<'0'){if(CH=='-')F=1;CH=getchar();}
	while(CH>='0'&&CH<='9'){X=(X<<1)+(X<<3)+CH-'0';CH=getchar();}
	return F?-X:X;
}
inline void Write(LL X){
	if(X<0)X=-X,putchar('-');
	if(X>9)Write(X/10);
	putchar(X%10+48);
}
void Update(LL X,LL V){
	LL I,J,K;
	Fp(I,X,N,LowBit(I)){
		A[I]+=V;
	}
	Fm(I,X,1,LowBit(I)){
		B[I]+=V;
	}
}
LL GetAns(LL X){
	LL I,J,K,Ans1=0,Ans2=0;
	Fp(I,X,N,LowBit(I)){
		Ans1+=B[I];
	}
	Fm(I,X,1,LowBit(I)){
		Ans2+=A[I];
	}
	return min(Ans1,Ans2);
}
int main(){
	LL I,J,K;
	N=Read();
	Fp(I,1,N,1){
		Tree[I].InPut(I,Read());Update(I,1);
	}sort(Tree+1,Tree+1+N);
	for(I=1;I<=N;I=J){
		for(J=I;Tree[J].Height==Tree[I].Height;J++){
			Update(Tree[J].ID,-1);
		}
		for(J=I;Tree[J].Height==Tree[I].Height;J++){
			Ans+=GetAns(Tree[J].ID);
		}
	}Write(Ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值