题解:P13257 [GCJ 2014 #2] Up and Down
题意相当明了,在这里总结出要点:第一是此题有多组测试数据;第二是输入为 \(n\) 以及一个长为 \(n\) 的序列,每次操作可将相邻元素交换位置。求将该序列转换为先单调递增后单调递减的最少操作数。
我们来看样例。样例一告诉我们可以全部单调递增或单调递减。样例二看完后就很明了了。我把样例二制成了一个条形图,方便读者看懂。

上方是操作前,下方是操作后。注意到最后完成所有操作后一定是有一个峰值。那么思路也就来了。
为了保证序列先升后降,我们从大到小依次确定每个元素的位置。确定最大元素后再确定次大元素,则次大元素的位置就会受到其制约。可见,大元素的位置会限制小元素的摆放,小元素必须放在大元素的外侧才能保证递增或递减。先确定大元素的位置,才能让小元素有正确的选择范围。
由于已处理的元素都比当前元素大,它们已经占据靠近峰值的位置,当前元素要放到目标位置就必须穿过这些已处理元素,每次穿过一个元素需要一次操作。因此分别记录它左右两边的已处理元素,即大于他的元素,取其中的较小值即可。
大体思路就这么多吧。细枝末节的东西和代码解释我就写在注释里了。本人无压行习惯,但实际上去掉注释和多余换行代码也不算很长。
代码如下:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
int solve() {
int n;
scanf("%d",&n);
vector<int>a(n);
map<int,int>mp;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
mp[a[i]]=i;
}
vector<int>b=a;//b数组用来排序
sort(b.begin(),b.end(),greater<int>());
// flag标记是否已处理
vector<bool>flag(n,0);
int ans=0;
// 从大到小处理每个元素
for(ri i=0;i<n;i++)
{
int x=b[i];
int xpos=mp[x];
// 计算左边已处理的数量
int l=0;
for(int j=0;j<xpos;j++)
{
if(flag[j])l++;
}
// 计算右边已处理的数量
int r=0;
for(ri j=xpos+1;j<n;j++)
{
if(flag[j])r++;
}
// 求最小移动次数(决定左移or右移)
ans+=min(l,r);
// 标记x为已处理
flag[xpos]=1;
}
return ans;
}
int main() {
int T;
cin>>T;
for(int qwq=1;qwq<=T;qwq++)
{
printf("Case #%d: %d\n",qwq,solve());
}
return 0;
}
本文来自博客园,作者:Circle_Table,转载请注明原文链接:https://www.cnblogs.com/Circle-Table/articles/19177414

浙公网安备 33010602011771号