题型归纳2-数组排序与交换

相邻交换

如果一个序列只允许交换相邻两个元素
那么交换的最小次数就是这个序列的逆序数

求逆序数(不超时)的方法
1.归并排序
2.树状数组

非相邻交换

如果一个数组允许交换任意两个元素
那么就要查找这个数组中的循环节的个数

最少的交换次数=n-循环节的个数
最坏的情况就是只有一个循环节 要交换n-1次

比如 2 3 1 这个序列只有1个循环节,那么3-1=2次,最少也要交换两次
再比如 1 3 2 有2个循环节 1-1和 3-2 那么最少交换的次数就是 3-2=1次

非相邻贪心交换

如果一个数组允许交换任意两个元素,但是每交换两个元素就要付出
a*| i-j | *b 的代价,怎么交换可以完成排序并且让代价最小
(其中 a,b,为常数,i,j为数组元素的下标)

这是一道贪心的题目

这个题刚开始还是很难想的,不过我们可以先引入几个简单的例子辅助思考

例1: 4 2 3 5 1
例2: 4 2 1 3 5

对于例1来说,最好的办法是
4 2 3 5 1→ 1先跟5交换位置,然后1再跟4交换位置
对于例2来说,最好的办法是
4 2 1 3 5 →4先跟1换位置,然后4再跟3换位置

我们发现一个规律,对于样例1来说,数字1可以借助数字5的位置当“跳板”跳回自己的位置
但是数字4却不可以借助数字“5”为跳板
对于样例2来说,数字4可以借助数字1当“跳板”跳回原位置,但是数字3却不能借助数字1
进行跳跃

因此我们发现,我们每次交换的两个数字必须是
正好一个想要往右走,正好一个想要往左走 才能达到最优
如果两个交换的数字是同方向,那么就会导致资源浪费

为了避免交换的操作会产生重复
我们特别规定:只交换那些位置偏后的数字,这样就可以避免交换操作时候的重复交换!

所以说如果有三个数字 a,b,c的情况
t[1]=a.t[2]=b.t[3]=c
设函数v(x)为a,b,c三个数字真正想去的位置
如果C目前的位置偏后,那么我们就想要先去对C进行操作
如果
v[a] < 3 ,那么就说明a数字的真正位置在当前C数字的前面
由于我们现在 交换的都是那些偏后的数字,这就导致了,在C前面的数字要么已经到了想要的位置
要么就是偏靠前的.那么不就代表 C可以借助他前面的某一个想要后移的数字作为”跳板“进行前移了吗
然后再次判断此“跳板”的真正位置,重复上述操作即可!

如果
v[a] > 3 ,那么就说明a数字的真正位置在C的后面,那么就代表C没有“跳板”可以辅助跳跃了,只能当
数字a的跳板,此时只能直接交换 a,c;

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,a,b;
ll res;
vector<int> v,sx;
vector<pair<int,int> > ans;
void swp(int x,int y){ 
    res += b + abs(x - y) * a;
    ans.push_back({x + 1,y + 1});
    swap(v[x],v[y]);
}
int main()
{
    cin>>n;
    for(int i = 0;i <n;i ++){
    	int x;cin>>x;
        v.push_back(x);  
		}
    cin>>a>>b;
    sx = v;
    sort(sx.begin(),sx.end());
    sx.erase(unique(sx.begin(),sx.end()),sx.end());
    for(int i = 0;i < n;i ++) 
	{
	v[i] = lower_bound(sx.begin(),sx.end(),v[i]) - sx.begin();
    } 
    for(int i = 0;i < n;i ++){
        if(v[i] < i){

		 
		  
            vector<int> temp;
            temp.push_back(v[i]);
  
            while(v[temp.back()] < i) 
			temp.push_back(v[temp.back()]);
			
            temp.push_back(i);
            
           
            int len = temp.size();
            for(int j = len-1;j >=1;j --) 
                swp(temp[j-1],temp[j]); 
        }
    }
    printf("%lld\n",res);
    ll len = ans.size(); 
    cout<<len<<endl;

    for(int i = 0;i < len;i ++) 
	printf("%d %d\n",ans[i].first,ans[i].second);
    return 0;
}

相关函数:
erase:删除某个元素,或者某一范围的所有元素
unique:将容器内的元素进行排序,把所有重复的元素放在容器的尾部,返回不重复元素个数
back:返回容器内最后一个元素

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值