「JOISC 2014 Day1」有趣的家庭菜园

「JOISC 2014 Day1」有趣的家庭菜园

前置技能:树状数组。

题解部分:

如果我们知道了目标序列,我们就可以构造一个序列P,它的每一个元素就是目标序列这一位置的元素在原序列的位置,那么答案就是原位置序列[也就是{1,2,3,···,n}]通过冒泡排序达到目标序列所需的交换次数目。而冒泡排序的交换次数就是P的逆序对的数量。

由于要满足那两个条件,那么最后得到的序列一定是一个单增,单减或是先单增,再单减。(不严格单增与单减,即可以相等)

故我们从大到小枚举IOI草,IOI草要么位于之前放在之前的IOI草左边,或者是右边。

一种合法顺序如下。(方框内写的是放的顺序)

题解需要

然后每放一次,都用树状数组求出放在左边的增加的逆序对,再求出放在右边的时候增加的逆序对。放在哪边增加的逆序对少就放在哪边。(这个贪心是正确的就是因为比他大的IOI草要么都放在它的左边,要么都放在它的右边)

当我们枚举到一堆高度相等的IOI草时怎么放呢?首先对于一个IOI草,记它放入序列左边时增加的逆序对为Lcost个,放在序列右边增加的逆序对为Rcost个,那么如果他在原序列中的相对位置越大,它的Lcost就会越大,它的Rcost就会越小。于是我们可以把 L c o s t R c o s t Lcost \leq Rcost 的IOI草放在序列左边,另一些放在右边。不难发现 L c o s t R c o s t Lcost\leq Rcost 的IOI草在原序列中的相对位置会小于 L c o s t > R c o s t Lcost > Rcost 的IOI草。又我们可以让这些IOI草互相之间产生的逆序对数量为0(当这些IOI草放置后他们在原序列中的相对位置与目标序列的相对位置相同)。这样子放肯定是最优的。实现方法就是先算出各自他们的答案,再在树状数组上更新。

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 300005
#define lowbit(x) x&-x
using namespace std;
int H[M];
int n;
bool cmp(int x,int y){
	return H[x]>H[y];
}
struct Bin{
	int num[M];
	void clear(){
		memset(num,0,sizeof(num));
	}
	void Add(int x){
		while(x<M){
			num[x]++;
			x+=lowbit(x);
		}
	}
	int sum(int x){
		int res=0;
		while(x){
			res+=num[x];
			x-=lowbit(x);
		}
		return res;
	}
}B;
int ID[M];
int stk[M];
void Solve(){
	B.clear();
	for(int i=1;i<=n;i++)ID[i]=i;
	sort(ID+1,ID+n+1,cmp);
	long long ans=0;
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(H[ID[i]]==H[ID[i+1]]&&i!=n){
			int top=0;
			for(int j=i;j<=n&&H[ID[i]]==H[ID[j]];j++)stk[++top]=ID[j];
			sort(stk+1,stk+top+1);
			for(int j=1;j<=top;j++)ans+=min(B.sum(stk[j]),cnt-B.sum(stk[j]));//先算答案 
			for(int j=1;j<=top;j++)B.Add(stk[j]);//再更新树状数组 
			cnt+=top;
			i+=top-1;
		}else {
			int res=B.sum(ID[i]);
			int add=min(res,cnt-res);//选取最小的 
			ans+=add;
			B.Add(ID[i]);
			cnt++;
		}
	}
	printf("%lld\n",ans);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&H[i]);
	Solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35320178/article/details/89221590