题目大意是:
每头牛都有一个对应的值a[i],现在给定一个初始的牛的序列,希望通过两两交换,能够使这些牛按值升序排列,每次交换都会耗费一个 a[i]+a[j]
希望耗费最小,求出这个最小耗费
个人觉得这道题还是蛮有意思的,虽然我wa了很多发,但还是很值得思考一下的
这是一个置换群问题,但是我们首先要根据其值排个序确定每头牛本来应该属于的位置,再根据现在所在的位置得到一个映射关系to[i]
将a[i]又用b[]数组保存,排序后,b[i]表示第i大的牛的值
我们找出这个置换群中的所有循环集,每个循环集分别讨论,我们总是找到循环集中值最小的牛占据了别的牛的位置pos,然后把这个pos这个位置交换给对应的牛
这样得到的是b[min]+b[pos],因为每头牛总要回到自己的位置上,假定循环集中有len个牛,那么所有牛都回到自己的位置上那么所有的b[i]都要加一遍
但加的过程希望尽可能的小,所以总是跟b[min]相加,这个是可以保证的,因为在一个循环集中,每次交换最多只能令一个数回到正确位置上,除非到了最后一步
,否则的话,如果同时回到了正确位置,那么这两个数可以独立出来作为一个循环集,这与它们在原来的循环集中是矛盾的
其实到了这里感觉总的思路已经是对的
但是自己就是在这里wa了
想了半天发现还有更优的情况
另外还有一种情况需要考虑就是这个循环集中位置的交换可以利用b[1](也就是最小的牛)来帮忙
是不是通过利用最小编号的牛帮助这个循环集排好队是不是结果更小
最小的牛加进来帮忙就是值为b[1]的牛和当前循环集内最小的牛交换一次位置,帮好忙后再交换回来
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 const int N = 10005; 7 const int M = 100005; 8 int a[N] , b[N] , to[N] , vis[N] , id[M];//to[]记录置换群上的映射关系 9 10 int circle(int u) 11 { 12 int v = u , ans = 0; 13 int cnt = 0; 14 while(u != to[v]){ 15 vis[v] = 1; 16 cnt++; 17 ans = ans+b[v]; 18 v = to[v]; 19 } 20 ans = ans+b[v] , cnt++; 21 vis[v] = 1; 22 if(cnt == 1) return 0; 23 else{ 24 /*这里要判断一下,是不是通过利用最小编号的牛帮助这个循环集排好队是不是结果更小 25 最小的牛加进来帮忙就是值为b[1]的牛和当前循环集内最小的牛交换一次位置,帮好忙后 26 再交换回来*/ 27 int tmp = ans + b[u] + (cnt+1)*b[1]; 28 return min(tmp , ans + (cnt-2)*b[u]); 29 } 30 } 31 32 int main() 33 { 34 // freopen("a.in" , "r" , stdin); 35 int n; 36 while(scanf("%d" , &n) != EOF) 37 { 38 for(int i=1 ; i<=n ; i++) 39 { 40 scanf("%d" , a+i); 41 b[i] = a[i]; 42 } 43 sort(b+1 , b+n+1); 44 for(int i=1 ; i<=n ; i++) 45 id[b[i]] = i; 46 for(int i=1 ; i<=n ; i++) 47 to[i] = id[a[i]]; //表示第i个位置被第id[a[i]]大的牛占据了 48 memset(vis , 0 , sizeof(vis)); 49 int ans = 0; 50 for(int i=1 ; i<=n ; i++) 51 if(!vis[i]) ans += circle(i); 52 53 printf("%d " , ans); 54 } 55 return 0; 56 }