ALDS1_6_D:Minimum Cost Sort (置换群) 同POJ 3270-Cow Sorting

题目链接:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_6_D

输入:给一组wi,要把他们从大到小排序,每次交换wi和wj的cost是wi+wj

输出:完成对wi的排序需要消耗的最小cost

先引入一个置换群的概念:一个有限集合的一一变换叫做置换,一对对置换组成了置换群。对于一个集合a(a[1],a[2],a[3]...a[n]) 通过置换可以变成 (b[a[1]],b[a[2]],b[a[3]]...b[a[n]]) b的作用就是置换(可以理解为某种函数的作用),将原来的集合映射成具有相应次序的集合a',a'可以看做是a的相同元素集合,不同的排列组合的一个集合。 每个n元的置换都可以表示成若干个互不相交的循环置换的乘积,设每个子循环置换的循环节为ci,则总的置换的循环节显然为lcm(c1,c2..cn)。

在这个题目中,置换的概念就是交换,然后在给定的序列中,找到所有的循环,然后对每一个循环进行操作,对这个循环内的值进行排序,得到这个循环的最小cost,然后把所有循环的cost相加就是整体循环。

比如对于一组输入的数,比如4 3 2 7 1 6 5,我们把它分成几个组,(4 7 1 5) (3 2) (6) 分成这三组,每一组是一个循环,这三个循环组成了一个置换群。那么是如何来找置换群的呢。我们先对这组数据进行排序,结果是 1234567 ,首先,对于第一个输入的值是4,这个4正确的位置应该是在第3个位置,但是现在第三个位置的数是7,所以这个循环的下一个元素是7;同样的,对于7,这个7正确的位置应该是在第6个位置,而现在的第六个位置的数是5,所以这个循环的下一个元素是5;同样的,对于5,这个5正确的位置应该是在第4个位置,而现在的第四个位置的数是1,所以这个循环的最后一个元素是1;最后,对于1,这个1正确的位置在第一个位置,而现在在第一个位置的元素正式我们的初始值4,所以这个循环构成了一个闭环,一个循环就找完了。

通过上述的过程,我们成功的把一个序列分割成了若干的循环,下面就要依次求出每个循环的cost值就可以了。

求cost值的过程可以用贪心的方法,就是希望能尽量少的移动wi比较大的元素,即通过循环内最小的数来移动其他元素,所以我们先把最小的那个数占着的位置的正确元素移过来。比如上述的第一个循环的(4 7 1 5) ,可见,1这个数占了5的位置,所以我们先用1这个数,把5和1换位置,得到(4 7 5 1),然后这时候的1占了7的位置,所以把1和7换位置,得到(4 1 5 7),最后把1和4划痕位置,得到(1 4 5 7),总消耗为(1+5)+(1+7)+(1+4)

用上述方法进行置换,设一个循环中一共有n个元素,每个元素是wi,每次置换的cost是(min(wi)+wi),wi自己不用和自己交换,那么此时的cost是∑wi-min(wi)+(n-1)*min(wi) 即是 ∑wi+(n-2)*min(wi)

但是还有一种置换的方法,是引入一个外援,假设这个外援是x,我们只需要在先把这个x换进来,最后把这个x换出去,就可以实现通过这个外援来进行元素的移动。每次置换的cost是(x+wi),但是要增加把x换进来和换出去的cost,显然,换进来和换出去的过程,都通过这个循环里的min(wi)来完成最省cost,所以额外增加的cost=(x+min(wi))(不用乘以2,因为移入的过程已经算入到第一次置换里了),所以最后的总cost=∑wi+n*x+x+min(wi)=∑wi+min(wi)+(n+1)*x

所以每一个循环的最终的cost的值就是两种方法中的最小值:

cost=min(∑wi+(n-2)*min(wi),∑wi+min(wi)+(n+1)*x)

最终代码如下:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxx 1010
#define maxn 10010

int C[maxn];

int main(){
	int n,ans=0;
	int A[maxx],B[maxx];
	bool V[maxx];
	cin>>n;
	for(int i =0;i<n;i++) {
		cin>>A[i];
		B[i]=A[i];
		V[i]=false;
	}
	sort(B,B+n);
	for(int i=0;i<n;i++) C[B[i]]=i;//C数组的value标识A[i]的值在B[i]中的位置,即排序后的正确位置
	int x=*min_element(A,A+n);//找出用于外援的最小的值
	for(int i=0;i<n;i++){
		if(V[i]==true) continue;
		int s=0,minn=maxn,c_len=0;
		int cur=i;
		while(V[cur]==false){
			c_len++;//循环的长度
			s+=A[cur];
			minn=min(minn,A[cur]);//找出min值
			V[cur]=true;
			cur=C[A[cur]];//循环的下一个元素
		}
		ans+=min(s+(c_len-2)*minn,s+minn+(c_len+1)*x);
	}
	cout<<ans<<endl;
	return 0;
}

 

错点:

 

 

1.一开始我的想法是写一个函数,输入A[i]的值,返回这个值在B[i]中的位置,后来发现得益于这个题目的A[i]的值的范围比较小,这个函数可以用桶排序的思想,使用一个计数数组C来完成。

2.遍历序列找循环的时候,要加上if(V[i]==true) continue;否则会导致出现此时的cost为负值的情况。

3.在while(V[cur]==false)循环内部,要用cur=i之后操作cur,不能直接操作i,否则会扰乱外层循环。

4.构建C数组的过程要在完成B的排序后进行,否则没有意义了

5.min_element返回值是一个迭代器指针,而min函数返回的是较小的值。这两个函数都可以用来操作其他类型(int char double)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值