我的思路是:记录下1,2,3的个数one, two, three,在前one个数里查,查到不是1的就从后面找到1进行swap。如果在前one个位置里找到2,就从two的带状位置里找1;如果在前one个数里找到3,就从three的带状位置里找1
等前one个数全整理为1,就在two的带状位置里找3,进行swap。
/*
ID: wangxin12
PROG: sort3
LANG: C++
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
#include <string>
using namespace std;
int data[1005];
int N;
int one, two, three;
int swap_times = 0;
void swap(int data[1005], int & first, int second) {
int temp = data[first];
data[first] = data[second];
data[second] = temp;
swap_times++;
}
bool find(int data[1005], int start, int end, int target, int index) {
for(int k = start; k <= end; k++) {
if(data[k] == target) {
swap(data, index, k);
return true;
}
}
return false;
}
int main() {
ifstream fin("sort3.in");
fin>>N;
for(int i = 0; i < N; i++) {
fin>>data[i];
if(data[i] == 1) one++;
if(data[i] == 2) two++;
if(data[i] == 3) three++;
}
fin.close();
int k = 0;
//先把1给理到最前面
for(k = 0; k < one; k++) {
if( data[k] == 2) {
//先找two位置里的1
bool flag = find(data, one, one + two - 1, 1, k);
//如果失败,找three位置里的1
if(!flag) flag = find(data, one + two, one + two + three - 1, 1, k);
}
if( data[k] == 3) {
//先找three位置里的1
bool flag = find(data, one + two, one + two + three - 1, 1, k);
//如果失败,找two位置里的1
if(!flag) flag = find(data, one, one + two - 1, 1, k);
}
}
//前one个1理顺了,理two位置里的3
for(k = one; k < one + two; k++) {
if( data[k] == 3) {
bool flag = find(data, one + two, one + two + three - 1, 3, k);
}
}
//Output
ofstream fout("sort3.out");
fout<<swap_times<<endl;
fout.close();
return 0;
}
根据NOCOW,有以下多种思路:
Way1
用l[i]记录i(1,2,3)出现的个数。排序后一定是l[1]个1,l[2]个2,l[3]个3这3段。
sn[i,j]记录在第i段中的j的个数。第i段中的j和第j段中的i交换,两个元素交换,只需要一次。所以取sn[i,j]和sn[j,i]中的较小数s,累加到总交换数中。
这样交换后,剩下的肯定是3段同时交换,最少需要2次。我们只需累加(sn[1,2]+sn[1,3])×2即可。
Way2
首先读入所有数,然后看原本的位置是“1”的里面有几个不是1,如果那几个不是1的数字在它原应该呆的位置中找到1的话就直接换,如果找不到就在另一堆中找。
比如说3 1 2三个数,第一个位置原应该是1,但是是3,那么就在原应该是3的位置中找1,但是是2,那么就在另一堆找,找到了1,那么把3跟1换一下,直到1全部到达自己的位置。然后在原应该是2的位置中找,如果碰到3就应该换一次,就这样找完就行了。
for i:=1 to n do
begin
readln(a[i]);
if a[i]=1 then inc(s[1,2])
else
if a[i]=2 then inc(s[2,2]);
end;
s[2,1]:=s[1,2]+1;
s[2,2]:=s[2,2]+s[2,1]-1;
s[3,1]:=s[2,2]+1;
这段代码是记录位置的,s[x,1]是x的初始位置,s[x,2]是x的终点位置。
记录好位置之后就按照上面的流程去找就好了,实现起来应该是很简单的。
Way3
这是极其诡异的做法: 只要类似与方法二,记录下本是N的位置却不是N的个数A[N],表示N这个数有多少个不在自己的位置上。
然后,开一个数组B,存储排序后的序列。每次比较原数列Data[i]和B[i],如果不相等,则dec(A[data[i]]),dec(A[b[i]]),inc(Total)。
本来只想着尝试这样的方法,没想到交上去后就AC了。莫名……只是求最小次数的问题这样可以AC……
Way4
O(n)预处理,O(1)计算(注:数字i该在的位置即排序后数字i所在的位置区间)。d[i,j]表示排序后数字i该在的位置中含有的数字j的个数。
那么,ans:=d[2,1]+d[3,1]+d[2,3]+max(0,d[1,3]-d[3,1])。
原理很简单,首先要把所有的1交换到最前面:用的次数是d[2,1]+d[3,1]。然后把所有的3交换到最后,这些3共两部分:一部分是在2该在的位置中的,这些需要d[2,3]次交换;另一部分是在1该在的位置中的,这些3中有一些是通过处理1已经回来了,还有一些可能在处理1的时候交换到了2该在的位置,就需要再交换一下。这部分交换总数是max(0,d[1,3]-d[3,1])。
我用的是这个方式的变形:ans:=d[1,2]+d[1,3]+max(d[2,3],d[3,2]);
处理解释:d[1,2]+d[1,3]是所有占据1位的2和3的数量,也就是不在正确位置的 1的数量(d[2,1]+d[3,1]),这两个加和是相同的。第一步求这个和,也就是让所有1归位。
此操作之后,存在两种情况。
一:原序列中不存在三值互换的现象,那么d[2,3]与d[3,2]应该是相同的。max算子不起作用。
二:原序列中存在三值互换的现象。可知,d[2,3]与d[3,2]中必有一个不变,而另一个在增大,且增大的量在数值上等于原三值交换的次数。就是例如上段文字所说:这些3中还有一些可能在处理1的时候交换到了2该在的位置,这时增加的就是d[2,3],而d[3,2]不变。由于交换是相对的,变化后的d'[2,3]与d'[3,2]必相等,又由于其中有一个值没有变动,所以该值也就等于原d[2,3]与d[3,2]中的最大值。
然后取这个最大值就可以让 2 3 同时归位。问题得解。
补充一点
我们用a[i,j]表示在i的位置上j的个数,比如a[2,1]=5就表示排好序后,上面应该是2,但现在被1占领的位置数是5。先贪心到不能两值内部交换。
那么操作之后不会存在a[2,1]>0和a[2,3]>0同时成立的现象。
反证法:比如交换之后a[2,1]>0,且a[2,3]>0则在1的位置上只能有3(1和2能内部相抵的已经全部抵消了),3的位置上只能有1(同理),那么1和3又可以内部交换了,与假设矛盾。得证。
还有最后3值交换是乘2,而不是乘3。