离散--脾气牛排队(cow sorting)

本文介绍了如何运用群论解决‘脾气牛排队’问题,即给定一组乱序的牛,按脾气值升序排列,每次交换两头牛的代价是它们脾气值之和。通过将置换转化为轮换,利用不相杂轮换和借助外援的策略,求得最小代价的排序方案。文中详细阐述了解题思路,并提供了代码实现。
摘要由CSDN通过智能技术生成

码作业日常bb

接上一篇文章置换的轮换表示,本篇文章记录的“脾气牛排队”也是一个很有趣的有关群论的问题。

这是学校布置的作业,原题来自http://poj.org/problem?id=3270

这道题我们老师给的测试用例一共有三个,
我写完第一个版本交上去之后发现编译错误,原来是评测系统的C语言标准不支持for循环的括号里定义循环变量,我日~
第二次编译通过了,运行超时。。。发现自己的代码有个变量有问题导致循环死掉了,一直没有跳出
第三次,反复调试,本地运行感觉没什么问题了,提交后发现,woc,这怎么不是满分,仔细一比较,发现最后一个用例没通过
第四次,最终版本,喜大普奔,终于accepted了也就是下面的代码
但是可能算法本身,或者代码规范仍然有点垃圾,但不管怎么说,它accepted了,它accepted了,它accepted了!!!
(这周还学到一个词叫程序的局部性,,好像是说优秀的程序员写出的程序往往具有较好的局部性,有利于利用cache,这样能提高运行速度,emmmm,对不起,咱不是优秀的程序员,,)

问题描述

Farmer John’s N (1 ≤ N ≤ 10,000) cows are lined up to be milked in the evening. Each cow has a unique “grumpiness” level in the range 1…100,000. Since grumpy cows are more likely to damage FJ’s milking equipment, FJ would like to reorder the cows in line so they are lined up in increasing order of grumpiness. During this process, the places of any two cows (not necessarily adjacent) can be interchanged. Since grumpy cows are harder to move, it takes FJ a total of X+Y units of time to exchange two cows whose grumpiness levels are X and Y.
Please help FJ calculate the minimal time required to reorder the cows and give the procedure of sorting.
输入:
第1行:N (整数N代表牛的数量)
第2行到N+1行:牛的脾气值
输出:
第1行:最小排队代价值
第2行:给牛按脾气值升序排列的排队过程(每行表示一对交换,脾气值空格分隔)

问题分析

输 入 的 是 一 个 乱 序 的 数 列 , 要 让 其 从 小 到 大 排 列 输入的是一个乱序的数列,要让其从小到大排列
每 次 只 能 对 换 数 列 中 的 两 个 数 , 每 次 交 换 的 代 价 是 被 交 换 的 两 个 数 的 和 。 每次只能对换数列中的两个数,每次交换的代价是被交换的两个数的和。
要 在 代 价 最 下 的 情 况 下 对 数 列 排 序 。 排 序 是 一 个 数 组 到 自 身 的 一 个 置 换 : 要在代价最下的情况下对数列排序。排序是一个数组到自身的一个置换:

( 5 4 1 3 2 9 8 6 7 1 2 3 4 5 6 7 8 9 ) \begin{pmatrix} 5 & 4 & 1 & 3 & 2 & 9 & 8 & 6 & 7 \\ 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \end{pmatrix} (514213342596876879)

根 据 群 论 的 知 识 , 每 个 置 换 可 以 写 成 一 组 不 相 杂 轮 换 的 乘 积 。 根据群论的知识,每个置换可以写成一组不相杂轮换的乘积。

( 5 4 1 3 2 9 8 6 7 1 2 3 4 5 6 7 8 9 )    ⟺    ( 5 1 3 4 2 ) ( 9 6 8 7 ) \begin{pmatrix}5 & 4 & 1 & 3 & 2 & 9 & 8 & 6 & 7 \\ 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9\end{pmatrix} \iff \begin{pmatrix}5 & 1 & 3 & 4 & 2\end{pmatrix} \begin{pmatrix}9 & 6 & 8 & 7\end{pmatrix} (514213342596876879)(51342)(9687)

只 需 要 在 每 个 轮 换 内 部 进 行 对 换 就 行 了 。 由 于 轮 换 是 不 相 杂 , 所 以 可 交 换 , 上 式 就 可 以 写 成 : 只需要在每个轮换内部进行对换就行了。由于轮换是不相杂,所以可交换,上式就可以写成: :

( 9 6 8 7 ) ( 5 1 3 4 2 )    ⟺    ( 9 7 ) ( 9 8 ) ( 9 6 ) ( 5 2 ) ( 5 4 ) ( 5 3 ) ( 5 1 ) 此 时 代 价 是 78 \begin{pmatrix}9 & 6 & 8 & 7\end{pmatrix} \begin{pmatrix}5 & 1 & 3 & 4 & 2\end{pmatrix} \iff \begin{pmatrix}9 & 7 \end{pmatrix} \begin{pmatrix}9 & 8 \end{pmatrix} \begin{pmatrix}9 & 6 \end{pmatrix} \begin{pmatrix}5 & 2 \end{pmatrix} \begin{pmatrix}5 & 4 \end{pmatrix} \begin{pmatrix}5 & 3 \end{pmatrix} \begin{pmatrix}5 & 1 \end{pmatrix} 此时代价是78 (9687)(51342)(97)(98)(96)(52)(54)(53)(51)78

但 是 我 们 要 让 代 价 最 小 , 就 要 让 轮 换 中 最 小 的 数 和 其 他 做 对 换 。 不 妨 先 调 整 一 下 轮 换 : 但是我们要让代价最小,就要让轮换中最小的数和其他做对换。不妨先调整一下轮换: :

( 6 8 7 9 ) ( 1 3 4 2 5 )    ⟺    ( 6 9 ) ( 6 8 ) ( 6 7 ) ( 1 5 ) ( 1 2 ) ( 1 4 ) ( 1 3 ) 此 时 代 价 是 60 \begin{pmatrix}6 & 8 & 7 & 9\end{pmatrix} \begin{pmatrix}1 & 3 & 4 & 2 & 5\end{pmatrix} \iff \begin{pmatrix}6 & 9 \end{pmatrix} \begin{pmatrix}6 & 8 \end{pmatrix} \begin{pmatrix}6 & 7 \end{pmatrix} \begin{pmatrix}1 & 5 \end{pmatrix} \begin{pmatrix}1 & 2 \end{pmatrix} \begin{pmatrix}1 & 4 \end{pmatrix} \begin{pmatrix}1 & 3 \end{pmatrix} 此时代价是60 (6879)(13425)(69)(68)(67)(15)(12)(14)(13)60

但 是 这 样 的 算 法 仍 然 不 是 最 优 的 , 即 使 在 某 些 情 况 下 能 够 的 到 最 优 解 。 这 种 算 法 在 轮 换 头 的 时 候 就 不 好 使 了 。 但是这样的算法仍然不是最优的,即使在某些情况下能够的到最优解。这种算法在轮换头的时候就不好使了。 使使 所 有 牛 中 脾 气 最 小 那 头 牛 1 , 设 正 在 处 理 的 这 个 轮 换 阶 数 为 k , 头 牛 是 t 所有牛中脾气最小那头牛1,设正在处理的这个轮换阶数为k,头牛是t 1kt
借 助 外 援 的 意 思 是 先 把 头 牛 与 外 援 对 换 再 把 外 援 和 原 来 本 该 和 头 牛 交 换 的 那 些 牛 交 换 , 最 后 再 把 外 援 和 头 牛 交 换 。 借助外援的意思是先把头牛与外援对换再把外援和原来本该和头牛交换的那些牛交换,最后再把外援和头牛交换。 ,
需 要 外 援 的 条 件 是 ( t ∗ ( k − 1 ) > ( t + 1 ) ∗ 2 + 1 ∗ ( k − 1 ) ) 即 在 不 借 助 " 外 援 " 1 的 情 况 下 这 轮 换 的 代 价 > 借 助 外 援 的 情 况 的 代 价 。 需要外援的条件是(t*(k-1) > (t+1)*2 + 1*(k-1)) 即在不借助"外援"1的情况下这轮换的代价>借助外援的情况的代价。 (t(k1)>(t+1)2+1(k1))""1>

( 6 9 ) ( 6 8 ) ( 6 7 ) ( 1 6 ) ( 6 1 ) ( 1 5 ) ( 1 2 ) ( 1 4 ) ( 1 3 ) \begin{pmatrix}6 & 9 \end{pmatrix} \begin{pmatrix}6 & 8 \end{pmatrix} \begin{pmatrix}6 & 7 \end{pmatrix} \begin{pmatrix}1 & 6 \end{pmatrix} \begin{pmatrix}6 & 1 \end{pmatrix} \begin{pmatrix}1 & 5 \end{pmatrix} \begin{pmatrix}1 & 2 \end{pmatrix} \begin{pmatrix}1 & 4 \end{pmatrix} \begin{pmatrix}1 & 3 \end{pmatrix} (69)(68)(67)(16)(61)(15)(12)(14)(13)
   ⟺    ( 1 6 ) ( 6 9 ) ( 6 8 ) ( 6 7 ) ( 6 1 ) ( 1 5 ) ( 1 2 ) ( 1 4 ) ( 1 3 ) 此 时 代 价 是 59 \iff \begin{pmatrix}1 & 6 \end{pmatrix} \begin{pmatrix}6 & 9 \end{pmatrix} \begin{pmatrix}6 & 8 \end{pmatrix} \begin{pmatrix}6 & 7 \end{pmatrix} \begin{pmatrix}6 & 1 \end{pmatrix} \begin{pmatrix}1 & 5 \end{pmatrix} \begin{pmatrix}1 & 2 \end{pmatrix} \begin{pmatrix}1 & 4 \end{pmatrix} \begin{pmatrix}1 & 3 \end{pmatrix} 此时代价是59 (16)(69)(68)(67)(61)(15)(12)(14)(13)59

解题思路

1. 输入牛的总数量N 初始乱序的牛
2. 把置换转换成为轮换 二维数组cycle[][],直接搞一个死循环
	  确定头牛first
	  如果满足退出条件就退出循环找一个轮换cycle[i][]
	  再来一个死循环
	       如果下一头牛和头牛一样就退出
	       否则把这头牛弄进来cycle[i][j]
3. 遍历轮换数组每一行
    每次循环只涉及到参与这次轮换的几头牛
    设每次参与对换的两头牛分别是x y,x是这个轮换的头牛
    if x满足借助外援的条件先对换换外援和这头牛,再正常操作,再对换这头牛和外援
    else 对换x y

代码实现

#include <iostream>
#define N 10
using namespace std; 

//查找target 是否在arr中 
int find(int target, int arr[N][N]){
	int i,j;
	for(i=0;i<N;i++) {
		for(j=0;j<N;j++) {
			if(arr[i][j] == 0) {
				break;
			}
			if(arr[i][j] == target) {
				//cout << target <<"位于 (" << i << "," << j << ")"<< endl; 
				return 1;
			}
		}
	}
	return 0; 
}

int main()
{
	int n;
	cin >> n; //输入牛的数量 
	
	int cows[N]; //乱序的牛
	int map[N];
	int i,j,k;
	int temp; //脾气 
	for(i=1;i<=n;i++) {
		cin >> temp;
		cows[i] = temp; 
	}
	for(i=1;i<=n;i++) {
		map[cows[i]] = i;
	}

	int cycle[N][N] = {0}; //轮换 每行一个 
	int rank[N] = {0};
	int first; //每个轮换的头牛脾气值 
	i = 0; //已经找到的轮换的个数 
	while(1) {
		j = 0;
		first = 0;
		
		//如果在之前所有轮换中都没有找到这头牛 
		//就这头牛作为头牛 找下一个轮换
		for(k=1;k<=n;k++) {
			if(find(cows[k],cycle) == 0) {
				first = cows[k];
				break;
			}
		} 
		// cout << "第" << i+1 << "轮的头牛脾气是:\t" << first << endl; 
		
		// 找到所有轮换就退出 
		if(first == 0)
			break;
		
		//根据头牛first找轮换
		int current = first;
		int next = map[current]; 
		
		while(1) {
			// 当前元素插入当前轮换尾部 
			cycle[i][j] = current;
			j++;
			if(next != first) {
				current = next;
            	next = map[current];
			}
			else {
				// 这次轮换共有几个数存入索引数组 以便输出 
            	rank[i] = j;
            	break;
			}
		}	
		i++;
	}
	
	int cycle_count = i; //轮换数量
	//cout << "轮换数量" <<i << endl; 	 
	
	
	//轮换调整 找到每个轮换的最小元素 把它置为头牛 
	for(i=0;i<cycle_count;i++) {
		int min = cycle[i][0];
		int flag = j;
		for(j=0;j<rank[i];j++){
			if(cycle[i][j] < min){
				min = cycle[i][j];
				flag = j;
			}
		}
		//cout << "轮换中最小的数是:cycle[" <<i << "][" << 
		//flag <<"]"<< min << endl; 
		for(k=0;k<flag;k++) {
			int t = cycle[i][0];
			//cout << "t=" << t << endl;
			for(j=0;j<rank[i]-1;j++) {
				cycle[i][j] = cycle[i][j+1];
			}
			cycle[i][j] = t;
		}	
	}
	
	k=0;
	int sum=0;
	int pairs[N][2] = {0};
	for(i=0;i<cycle_count;i++) {
		int first = cycle[i][0];
		int condition = first*(rank[i]-1) > 2*(first+1)+(rank[i]-1);
		if(condition){
			//cout << "借助外援" << endl; 
			first = 1;
			//cout << 1 << "," <<cycle[i][0] << endl;
			pairs[k][0] = first;
			pairs[k][1] = cycle[i][0];
			sum += pairs[k][0] + pairs[k][1];
			k++;
			for(j=1;j<rank[i];j++) {
				//cout << first <<","<< cycle[i][j] << endl;
				pairs[k][0] = first;
				pairs[k][1] = cycle[i][j];
				sum += pairs[k][0] + pairs[k][1];
				k++;
			}
			//cout << "有借有还" << endl;
			//cout << 1 << "," <<cycle[i][0] << endl;
			pairs[k][0] = 1;
			pairs[k][1] = cycle[i][0];
			sum += pairs[k][0] + pairs[k][1];
			k++;
		}
		else {
			for(j=1;j<rank[i];j++) {
				//cout << first <<","<< cycle[i][j] << endl;
				pairs[k][0] = first;
				pairs[k][1] = cycle[i][j];
				sum += pairs[k][0] + pairs[k][1];
				k++;
			}
		}
		//cout << endl;
	}
	
	cout << sum << endl;
	//每对换一次 输出此时的排序 
	int pairs_count = k;
	for(i=0;i<pairs_count;i++) {
		int x = pairs[i][0];
		int y = pairs[i][1];
		//cout <<"第"<<i<<"轮对换("<<x<<","<<y<<"): ";
		cows[map[x]] = y;
		cows[map[y]] = x;
		int t = map[x];
		map[x] = map[y];
		map[y] = t;
		for(j=1;j<=n;j++) {
			cout << cows[j] << " ";
 		}
		cout << endl;
	} 	
	return 0;
} 

欢迎各位大佬批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值