题目链接:POJ3270
题目大意:给你一组序列,目标为有序序列,每次只能交换两个数,每次的花费为交换的两个值的和,求如何交换使得花费最少。
知识点:置换群循环节
我们知道,当一个循环节中最小的值与这个循环节中所以其他值交换,花费最少,并且交换完后有序。
假设循环节长度为k,循环节中最小值为m,那么只需要用最小值m与其他值交换k-1次,即可有序。代价这样算,除最小值m之外的数,只需要一次就可以到自己的有序位置上,所以这些数的代价为 sum-m,而最小值m交换了k-1次,产生了(k-1)*m,代价,所以这种情况的总代价为:sum+(k-2)*m;
在考虑另外一种情况,考虑到整个序列的最小值为minm(此最小值不在当前循环节中),当用更小的值去交换当前循环节中的值,所花费代价更小。怎么考虑代价呢?我们先把minm与m交换,也就是说把minm放进当前循环节,在重复以上操作,代价为:sum-m+minm+(k-1)minm,然后再把minm与m交换,这样一进一出,产生的代价为 2(minm+m)
所以总的代价为 sum+minm*k+m
对每个循环节的交换代价取这两种情况的最小值即可,我应该说清楚了吧……其他在代码中有注释
/*
2017年8月1日16:45:22
POJ3270
置换群求循环节
AC代码
*/
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=100000+10;
const int inf=0x3f3f3f3f;
int a[maxn],b[maxn];
int n,sum,tot;
int min_num;//记录序列中最小值
struct node{
int min_val;//循环节中最小值
int num;//循环节长度
}c[maxn];
bool vis[maxn];//访问标记
void dfs(int val){
for(int i=0;i<n;i++){
if(b[i]==val&&!vis[i]){
vis[i]=true;
c[tot].num++;
c[tot].min_val=min(c[tot].min_val,b[i]);
dfs(a[i]);
}
}
}
int main(){
while(~scanf("%d",&n)){
sum=0;min_num=inf;
memset(vis,false,sizeof(vis));
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
b[i]=a[i];//将a数组复制到b数组
min_num=min(min_num,a[i]);//保存序列中最小值
sum+=a[i];//求序列和
}
/*对a数组进行排序-即变换的目标序列*/
sort(a,a+n);
/*循环节个数total*/
tot=0;
/*求出所有循环节包括长度和每个循环节中最小值*/
for(int i=0;i<n;i++){
/*如果该点访问过<=>该点在另外一个循环节中->跳过*/
if(vis[i]) continue;
else{
/*循环节初始化,最小值初始化为第一个值
长度初始化为1,该点打标记
然后进入对应序号的b[i]
举个例子:b数组:2 3 1
a数组:1 2 3
先访问的是b[0]=2,然后我们在访问 a[0]也就是2对应的1
DFS搜索,在从b数组中找到等于a[0]的值的下标i,此时
b[i]=b[2]=1,下标2打标记,我们再访问a[2],如此循环即可
直到访问到已经访问过的点<=>回到了循环的起始点->结束搜索
第一个循环节就找到了,应该说明白了吧:)*/
c[tot].min_val=b[i];
c[tot].num=1;
vis[i]=true;
dfs(a[i]);
tot++;
}
}
for(int i=0;i<tot;i++){
sum+=min((c[i].num-2)*c[i].min_val,c[i].min_val+(c[i].num+1)*min_num);
}
printf("%d\n",sum);
}
return 0;
}