题目描述
排序是一种很频繁的计算任务。现在考虑最多只有三值的排序问题。一个实际的例子是,当我们给某项竞赛的优胜者按金银铜牌序的时候。 在这个任务中可能的值只有三种 111,222 和 333。我们用交换的方法把他排成升序的。 写一个程序计算出,给定的一个由 1,2,31,2,31,2,3 组成的数字序列,排成升序所需的最少交换次数。
输入
第一行输入 NNN(1≤N≤10001 \le N \le 10001≤N≤1000)。
第 222 到 N+1N+1N+1 行,每行一个数字(111、222 或 333),共 NNN 行。
输出
共一行,一个数字。表示排成升序所需的最少交换次数。
输入输出样例
样例输入 #1
复制
9
2
2
1
3
3
3
2
3
1
样例输出 #1
复制
4
分析:
这道题难点有一个,就是如何换位,和谁优先换位 。那么在做题之前呢我们先要了解,因为就只有三个数,是不是我们把1和2都排序好,是不是就完成任务了呢。所以我们只需要对1和2进行处理就行。
对于和谁换位的问题,我们要优先和在其位置上的数换。举个例子,对于下面的数列,有三个1,两个2,三个三,1 3 2 1 2 3 1 3,分别记为a[1],a[2]...a[8],你要怎么做呢,我们要换a[2]数字“3”,那么是和a[4]换呢还是和a[7]换呢,答案肯定是和a[7]换对吧,因为这样的话我们在后期就会比和a[4]换少一步,因为我们不用再去处理那个3了,他在那待着就行了对吧。
好,道理我们都懂,可是要怎么实现呢,哎我们可以分情况讨论:我们先把1,2的数量还有所有总数记下来,记作sum1,sum2 ,n,那么1到sum1就是所有的1该待的地方,sum1+1到sum1+sum2就是2改待的地方,同理sum1+sum2+1到n就是3该待的地方。
当我们在遍历1的地盘1到sum1:
如果在这个点的是2,我们是不是要优先从二的地盘找2来换啊,好那么就在sum1+1到n这里找2,有同学可能问了,为什么要到n呢,为什么不是sum1+sum2呢,因为有可能2的地盘里没有2,所以我们还要往后再找这样才能保证一定可以找到2;
同理,如果这个点是3呢,那我们就从n开始倒着找,因为3的地盘肯定靠着n往前的。
当我们在遍历2的地盘sum1+1到sum1+sum2,注意这个时候1肯定是都排好了,往后的数要么是2,要么是3,那就不用再分情况了呗:
如果这个点是3,那我们还是从n开始倒着找,因为3的地盘肯定靠着n往前的。
OK!这样我们就完成了我们大体思想,下面上代码!
代码:
#include<iostream>
using namespace std;
int n;
int arr[10005];
int num1, num2, ans, sum1, sum2;//num1,sum1是1的个数,num2,sum2是2的个数
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> arr[i];
if (arr[i] == 1)//记录
num1++;
if (arr[i] == 2)
num2++;
}
sum1 = num1, sum2 = num2;
for (int i = 1; i <= n; i++)
{
if (num1 == 0 && num2 == 0)//如果1和2的数量都是0了,那么就停止遍历就可以了
break;
if (num1 != 0 && arr[i] != 1)//如果1还有没排好的,遍历到的点还不等于1,那么就该换位了
{
if (arr[i] == 2)//如果这个点是2的话
{
for (int j = sum1 + 1; j <= n; j++)//从sum1+1开始往后遍历找1,因为在sum1之前的1都不用动他,早晚sum1之前都得是1
{
if (arr[j] == 1)//找到了
{
int t;//换!
t = arr[i];
arr[i] = arr[j];
arr[j] = t;
ans++;//换的次数加一
break;//找到一个就行了,记得要break
}
}
}
else if (arr[i] == 3)//如果这个点是3的话
{
for (int j = n; j >= 0; j--)//下面的步骤都同理
{
if (arr[j] == 1)
{
int t;
t = arr[i];
arr[i] = arr[j];
arr[j] = t;
ans++;
break;
}
}
}
}
if (num1 == 0 && arr[i] != 2)//如果1已经没了,说明该排序2了
{
for (int j =n; j >=num1+num2+1; j--)//2的话就不用和1那样麻烦了,因为后边的点不是2就是3,不用在分类了,如果是3的话,那么我们从最后边到sum1+sum2+1开始遍历
{
if (arr[j] == 2)//找到了
{
int t;//下面都同理
t = arr[i];
arr[i] = arr[j];
arr[j] = t;
ans++;
break;
}
}
}
if (num1 != 0 && arr[i] == 1)//最后看一看正在遍历的这个点是不是1,是的话num1--
num1--;//为什么要写到最后来判断呢,这样的话可以不用分类,就是不用去区分这个点是不是需要换位的点
if (num1 == 0 && arr[i] == 2)//最后看一看正在遍历的这个点是不是2,是的话num2--
num2--;
}
cout << ans;
return 0;
}