题解: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;
}
posted @ 2025-10-30 16:14  Circle_Table  阅读(1)  评论(0)    收藏  举报