【题解】有趣的家庭菜园 / たのしい家庭菜園

文章同步发表于 CSDN洛谷Hexo

【题解】有趣的家庭菜园 / たのしい家庭菜園

双倍经验 三倍经验

Description

给定 \(h_{1\sim n}\),求最少交换几次可以使得原序列 满足 \(h_1\sim h_{i-1}\)\(h_{i+1}\sim n\) 中同时存在某个 \(\geqslant h_i\) 的数。

Solution

总感觉这题就思维难度与实现难度而言评不到紫,差不多蓝的亚子

看到交换等关键词就知道与逆序对有关,这里提供一个形象化的思路。

先思考如下特殊情况:\(h\) 中没有重复元素。

假设 \(h_x\)\(h\) 中的最小值。

那么,\(h_x\) 不管在中间那个位置,两边都会有比它大的。

所以 \(h_x\) 应该挪到序列的两端。具体是哪一端呢?移到不同的地方会不会对其他数带来不同的影响呢?

这就要根据 \(x\)\(1\)\(n\) 的距离来判断了,因为不管 \(h_x\) 移到哪一端,中间剩下的数排列顺序都是一样的,所以 \(h_x\) 对中间的那些数造成的影响是一样的,自然是移到距离更近的一端更优。

我们把这个操作看做将 \(h_x\)\(h\) 中删去,那么剩下的数又构成了一个新的问题。重复这个操作即可得到答案。

但我们不可能模拟这个过程计算。

所以我们有必要搞明白如下这件事:

假设进行到某一步时,当前的最小值为 \(h_y\),那么就应该这么移动:

图示

所以 \(h_y\) 往两边跳的过程,其实一共跳过的是 \(h_y\) 左边或右边 \(>h_y\) 的数的个数。

不过,这只是 \(h\) 中元素不重复的情况,有重复的情况呢,其实同理。

如果按从左到右的顺序处理序列,当 \(h_y\) 向左跳时,左边未归位的数中一定没有与 \(h_y\) 相等的值。(因为从左向右处理,在处理 \(h_y\) 之前已经把前面与 \(h_y\) 相等的值挪好了,所以不用担心左边)

但是,如果 \(h_y\) 要向右跳呢?右边可能有相等的数,所以要跳过的是 \(\geqslant h_y\) 的数。

当然,如果你喜欢,也可以从右到左,那么此时 \(h_y\) 向左就要跳过 \(\geqslant h_y\) 的数,向右跳过 \(>h_y\) 的所有数。

那,这题用树状数组维护一下不就出来了吗?

但是,我们考虑一种情况,以下是这种情况的最简形式:

5
2 2 1 1 2

按照我们刚刚的步骤,这一组数据应该像这样处理:

\(\to2_{(1)}\ 2_{(2)}\ 1_{(3)}\ 1_{(4)}\ 2_{(5)}\)

\(\to h_y=1_{(3)}\)

\(\to1_{(3)}\) 向右挪至 \(2_{(5)}\) 的位置,经历 \(2\) 次交换操作,此时总交换次数 \(ans=2\)

\(\to 2_{(1)} \ 2_{(2)}\ 1_{(4)}\ 2_{(5)}\ 1_{(3)}\)

\(\to h_y=1_{(4)}\)

\(\to 1_{(4)}\) 向右挪至 \(2_{(5)}\) 的位置,经历 \(1\) 次交换操作,此时总交换次数 \(ans=3\)

\(\to 2_{(1)} \ 2_{(2)}\ 2_{(5)}\ 1_{(4)}\ 1_{(3)}\)

\(\to\) 操作完成,最终答案 \(ans=3\)

但相信我们大家都能看出来,正确的交换次序是:

\(\to2_{(1)}\ 2_{(2)}\ 1_{(3)}\ 1_{(4)}\ 2_{(5)}\)

\(\to h_y=1_{(4)}\)

\(\to1_{(4)}\) 向右挪至 \(2_{(5)}\) 的位置,经历 \(1\) 次交换操作,此时总交换次数 \(ans=1\)

\(\to 2_{(1)} \ 2_{(2)}\ 1_{(3)}\ 2_{(5)}\ 1_{(4)}\)

\(\to h_y=1_{(3)}\)

\(\to 1_{(3)}\) 向右挪至 \(2_{(5)}\) 的位置,经历 \(1\) 次交换操作,此时总交换次数 \(ans=2\)

\(\to 2_{(1)} \ 2_{(2)}\ 2_{(5)}\ 1_{(3)}\ 1_{(4)}\)

\(\to\) 操作完成,最终答案 \(ans=2\)

咦?那么不能从左到右,只能从右到左咯?

当然不行,把刚刚的数据反过来输就可以 Hack 这种做法(

这说明了我们只能适当地在不同时机选择不同的选择次序,但是我们不难发现,最终都是要把位置更靠外的数往更外面的地方挪,比如上例中的 \(1_{(4)}\) 就比 \(1_{(3)}\) 更靠外(右),处理次序比 \(1_{(3)}\) 更优先, 最终的位置也比 \(1_{(3)}\) 更靠外(右)。

而上面那条简单的规律,就帮我们免去了决定选择次序的痛苦。

因为按以上的规律,越靠外的数,处理次序更优先,所以当我们处理一个数 \(h_y\) 时,\(h_y\) 一定是当前序列中值最小的数中位置最靠外的,所以直接查询 \(>h_y\) 的数的个数,就是 \(h_y\) 要交换的次数。

Code

树状数组和离散化相信各位都会吧。在这里我们用两个树状数组当做桶,分别维护当前 \(h_y\) 左边和右边的所有值的个数。

不过有个坑点在于必须输出文末回车,不然会 WA。

#include<cstdio>
#include<algorithm>
#define int long long
const int maxn=3e5+5;
const int LEN=(1<<20);
int n,a[maxn];
int ans1,ans2,ans;
int Bit[2][maxn],Lsh[maxn];
inline int Lowbit(int x){return x & (-x);}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}
inline void Update(int k,int x,int typ){
	for(int i=k;i<=n;i+=Lowbit(i))
		Bit[typ][i]+=x; 
	return; 
}
inline int Sum(int k,int typ){
	int ans=0;
	for(int i=k;i;i-=Lowbit(i))
		ans+=Bit[typ][i];
	return ans;
}
inline int nec(){
    static char buf[LEN],*p=buf,*e=buf;
    if(p==e){
        if((e=buf+fread(buf,1,LEN,stdin))==buf)return EOF;
        p=buf;
    }
    return *p++;
}
inline bool read(int&x){
    static char neg=0,c=nec();
    neg=0,x=0;
    while((c<'0'||c>'9')&&c!='-'){
        if(c==EOF)return 0;
        c=nec();
    }
    if(c=='-'){
        neg=1;
        c=nec();
    }
    do{x=x*10+c-'0';}while((c=nec())>='0');
    if(neg)x=-x;
    return 1;
}
signed main(){
	read(n);
	for(int i=1;i<=n;++i){
		read(a[i]);
		Lsh[i]=a[i];
	}
	std::sort(Lsh+1,Lsh+1+n);
	int cnt=std::unique(Lsh+1,Lsh+1+n)-Lsh-1;
	for(int i=1;i<=n;++i){
		a[i]=std::lower_bound(Lsh+1,Lsh+cnt+1,a[i])-Lsh;
		Update(a[i],1,1);
	}
	for(int i=1;i<=n;++i){
		Update(a[i],-1,1);
		ans1=n-i-Sum(a[i],1);
        Update(a[i],1,0);
        ans2=i-Sum(a[i],0);
		ans+=min(ans1,ans2);
	}
	printf("%lld\n",ans);
	return 0;
}

end.

posted @ 2021-05-02 00:25  XSC062  阅读(267)  评论(0编辑  收藏  举报