这类题型,一般有两种,一种是交换相邻元素,一种是交换任意元素。
交换相邻的最少次数:
对于从小到大的每个数,先从第一个的逆序对算起,然后把它往前放到第一个,再到第二个,再继续往最前放,也是加上逆序对。
最后其实就等于加上每个数原本的逆序对,因为从大到小的操作,能保证我移动过后,这个元素放到前面也不会增加我其他的逆序对。
交换任意的最少次数:
对于一系列元素,把元素和其有序时候的位置建立边(位置->位置),即可以发现:
举例:3 1 2 5 4
位置1(3)->位置3(2)->位置2(1)->位置1。是一个循环节,也是一个有向环。
对于这个环:我们内部排序只需要len(环)-1次即可。
总共次数:len(序列)-num(循环节个数)。
P2127 序列排序
题意:交换任意元素达成有序,每一次操作的贡献是两元素的和,问总贡献最少多少。
求循环节:循环节内部肯定是和最小交换达成结果贡献最少。
但还需要考虑的是:外部最小的交换进来+用外部最小的来交换有序+把最小的换回来的贡献可能会更小。
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<vector>
#include<set>
#include<cctype>
#include<string>
#include<queue>
#define rep(a,b) for(register int i=a;i<=b;i++)
#define red(a,b) for(register int i=a;i>=b;i--)
#define ULL unsigned long long
#define LL long long
using namespace std;
struct node
{
LL num;
int ind;
}arr[100050];
int visit[100050];
int cmp(node a,node b)
{
return a.num<b.num;
}
int main()
{
int n;
cin>>n;
vector<node>s;
arr[0].num=-100;
arr[0].ind=0;
s.push_back(arr[0]);
rep(1,n)
{
scanf("%lld",&arr[i].num);
arr[i].ind=i;
s.push_back(arr[i]);
}
sort(s.begin(),s.end(),cmp);
/* rep(1,n)
{
cout<<s[i].num<<" "<<s[i].ind<<endl;
}
*/
vector<LL>temp;
LL sum=0;
rep(1,n)
{
if(!visit[i])
{
temp.clear();
int j=i;
while(visit[j]==0)
{
temp.push_back(s[j].num);
visit[j]=1;
j=s[j].ind;
}
sort(temp.begin(),temp.end());
if(temp.size()>1)
{
LL tot=0;
for(int k=0;k<temp.size();k++)
{
tot+=temp[k];
}
sum+=min(tot+temp[0]+(temp.size()+1)*s[1].num,tot+temp[0]*(temp.size()-2));
}
}
}
cout<<sum<<endl;
}