USACO Sorting A Three-Valued Sequence (sort3)

来源:互联网 发布:算法怎么入门 编辑:程序博客网 时间:2024/05/17 08:44

我的思路是:记录下1,2,3的个数one, two, three,在前one个数里查,查到不是1的就从后面找到1进行swap。如果在前one个位置里找到2,就从two的带状位置里找1;如果在前one个数里找到3,就从three的带状位置里找1
等前one个数全整理为1,就在two的带状位置里找3,进行swap。

/*ID: wangxin12PROG: sort3LANG: 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位置里的1bool flag = find(data, one, one + two - 1, 1, k);//如果失败,找three位置里的1if(!flag) flag = find(data, one + two, one + two + three - 1, 1, k);}if( data[k] == 3) { //先找three位置里的1bool flag = find(data, one + two, one + two + three - 1, 1, k);//如果失败,找two位置里的1if(!flag) flag = find(data, one, one + two - 1, 1, k);}}//前one个1理顺了,理two位置里的3for(k = one; k < one + two; k++) {if( data[k] == 3) { bool flag = find(data, one + two, one + two + three - 1, 3, k);}}//Outputofstream 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。


原创粉丝点击