poj 3270 Cow Sorting (组合数学 置换应用)
大致题意:Farmer John有N头牛(1 ≤ N ≤ 10000),这N头牛都很各应,各自有一个不同的脾气脾气指数L(1 ≤ L ≤ 100000),这N头牛按脾气指数是无序排列,指数越大的越容易破坏farmer的挤奶器,所以farmer为了保护他的设施,要对这些牛按脾气指数递增的顺序排列,但交换两头牛的代价是这两头牛的脾气指数只和,现在告诉你牛的个数N和N头牛的脾气指数Li,求最小代价。
Time Limit: 2000MSMemory Limit: 65536K
样例:
这题跟黑书上的“无聊的排序”(P247)是一个题,而且这题目竟然是ACM/ICPC 02年Final的题啊我去,憋了很久想的思路竟然仅仅是半对的......,果断YM神题了。
思路:仔细读题,看到FARMER是两两交换牛的顺序进行排序的话,应该就往置换上靠拢,而这个题果然是置换的应用(有的解题报告上说是置换群,其实这只是单个置换,不用让它构成群)。我们来将这些无序的牛抽象成一个置换,一次移动就是对一对元素置换。举个例子:
那么我们用置换环表示应该是(1 6 5)(2 3 4)这样两个3阶环,也就是说这六头牛是两个无序子序列,每个子序列内部按坐标排好序则整体有序,既然要使代价最小,我们就应该让置换的次数尽量少,置换的两个数和尽量小。根据上面的划分,我们知道在一个长度为m的环中至少要移动m-1次使它有序,这样我们就能保证次数最少。那怎样使置换的数尽量小呢?我们知道,所有置换可以拆成对换(2阶环)乘积的形式,不妨将上面的环拆开,(1 6 5)可以拆成(1 6)(1 5),或者(6 5)(6 1),或者(5 1)(5 6),其实这就是3种置换方案。不难发现其中最优的方案是找出环中的极小元素将他分别和环中的其他元素置换一轮,形象点就是从这一波牛中找到那个脾气最小的当媒介,让它换脾气大的牛到相应的位置。貌似这个问题就解决了,我的思路也只是走到了这里。
但是这个思路是不完全正确的。如果给出的置换中每个环中的那个极小元素就是置换中的最小元素,那上面的思路肯定正确。但如果最小元素不在这个环里,那存不存在更优的策略?答案是存在的。试试1 8 9 7 6这组数据。将它划分成(8 6 9 7)(1),(没写出下标,直接按权划分的),那么我们把1和6交换,让1加入到前面的环中置换那另外三个大数,最后再把6换出来,发现得到的话费比上面给出的策略更优。也就是说置换的次数最少不一定能得到最优。那除了上面给出的两种策略还有其他的更优策略么?从贪心的角度分析不可能有更优了,所以我们只要对上面的两种策略中选择一个较小的即可。 给出两种方案的计算:
第一种策略:sum1=(L1+Min(L))+(L2+Min(L))+...+(Lm-1+Min(L)) 其中:sum1为总花费,Li为此环中的牛脾气质数,除去那个极小的一共m-1个,Min(L)为脾气最小的牛。
整理一下得到:sum1=sum(L)+(m-2)*Min(L)
第二种策略:sum2=(L1+MIN)+(L2+MIN)+...+(Lm-1+MIN)+2*(Min(L)+MIN) 其中MIN为N头牛中脾气最小的
整理一下得到:sum2=sum(L)+Min(L)+(m+1)*MIN
每个环都按两种策略找到较小累加就是总最小话费。至于如何求循环节,用计数排序即可,因为计数排序能求出元素在正序下的下标,计数排序的时间复杂度是O(n+k)其中n是排序元素个数,k是元素最大值,计数排序是时间复杂度非常低的非比较排序算法,但是应用性比较差,不懂原理的可以维基百科,讲的很好。题目难度不小,数学+贪心的一道题目。
最后多扯一句:这个题中每头牛的脾气指数是不同的,难度小了不少,如果有相同的怎么办?我没想出好的解决办法,有知道的大神望不吝赐教。
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 const int MAXNUM=10005; 5 bool vis[MAXNUM]; 6 int num[MAXNUM],pos[MAXNUM*10]; 7 int n,Max,Min; 8 void countSort(){ //计数排序 9 Max=-MAXNUM*10,Min=MAXNUM*10; 10 memset(pos,0,sizeof(pos)); 11 for(int i=1;i<=n;i++){ //标记存在的元素,并找到最大和最小值 12 pos[num[i]]=1; 13 if(num[i]<Min)Min=num[i]; 14 if(num[i]>Max)Max=num[i]; 15 } 16 for(int i=1;i<=Max;i++){ //找到i在正序中的位置 17 pos[i]+=pos[i-1]; 18 } 19 } 20 int solve(){ 21 int ans=0; 22 memset(vis,0,sizeof(vis)); 23 for(int i=1;i<=n;i++){ 24 int len=0,temp=i,sum=0,tMin=MAXNUM*10; 25 while(!vis[temp]){ //寻找一个循环节 26 vis[temp]=true; 27 len++; 28 sum+=num[temp]; 29 if(num[temp]<tMin)tMin=num[temp]; 30 temp=pos[num[temp]]; 31 } 32 if(len>0){ 33 int res1=sum+(len-2)*tMin,res2=sum+tMin+(len+1)*Min; 34 ans+=res1<res2?res1:res2; 35 } 36 } 37 return ans; 38 } 39 int main(){ 40 int i,j; 41 while(~scanf("%d",&n)){ 42 for(i=1;i<=n;i++){ 43 scanf("%d",&num[i]); 44 } 45 countSort(); 46 printf("%d\n",solve()); 47 } 48 return 0; 49 }