【题解】有趣的家庭菜园 / たのしい家庭菜園
【题解】有趣的家庭菜园 / たのしい家庭菜園
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.
—— · EOF · ——
真的什么也不剩啦 😖