相邻交换
如果一个序列只允许交换相邻两个元素
那么交换的最小次数就是这个序列的逆序数
求逆序数(不超时)的方法
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:返回容器内最后一个元素