题目大意:
给出一个N,并给出N个数(可能重复),然后要求用1~N内未使用的数替换其中重复的数,形成一个排列,使这个排列的字典序最小,求需要替换的个数和字典序最小的排列。
Examples
Input
4 3 2 2 3
Output
2 1 2 4 3
Input
6 4 5 6 3 2 1
Output
0 4 5 6 3 2 1
Input
10 6 8 4 6 7 1 6 3 4 5
Output
3 2 8 4 6 7 1 9 3 10 5
思路:
很容易想到,需要替换的个数就是在给出的数字中1~N中没有出现的数字的个数。比如第一组数据,3 2 2 3,只有3和2,缺少1和4,所以需要替换2个数。那么怎么替换才能使字典序最小呢?设当前最小的未被使用的数字为x,x只能去替换那些重复出现的数字,如果x小于最左端的重复出现的数字,那么用x替换后,字典序一定比之前小,所以此时可以替换。
而如果x大于最左端的重复出现的数字呢?那么将不能替换,因为替换后字典序比原来大了。但是如果当前这个数之前已经出现过一次,那么即使x比当前这个数大,也要替换,否则这个数就会重复出现。这也是本题的一个难点,即当x大于最左端的重复出现的数字时,怎么判断是否该替换。
我们发现,当x大于最左端的重复出现的数字并且这个数是第一次出现时则不能替换,否则就应该替换。
具体实现:
我们用一个vis数组来记录每个数出现的次数,如果数字 i 未出现过,则记录替换的个数并用它来替换其他重复出现的数,如果 i 比当前重复出现的数小或者当前数之前已经出现过一次,则替换;如果大,则不替换并把vis设为-1,意为该数之前已经出现过一次。
#include<stdio.h>
#include<string.h>
int a[200010],vis[200010];
int main()
{
int i,j,n,ans;
while(~scanf("%d",&n))
{
memset(vis,0,sizeof(vis));
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
vis[a[i]]++; //记录a[i]出现的次数
}
j=0; //j指向要处理的a数组的下标
ans=0; //ans为要替换的数的个数
for(i=1;i<=n;i++)
{
if(vis[i]==0)
{ //如果当前数未被使用,则用它替换其他数
ans++;
while(j<n)
{
if(vis[a[j]]>1||vis[a[j]]<0)
{ //如果当前数重复出现或者当前数不是第一次出现时
if(i<a[j]||vis[a[j]]<0)
{ //如果未使用的数i比当前数小或者当前数不是第一次出现
vis[a[j]]--; //个数-1
a[j]=i; //替换
j++; //处理下一个
break;
}
//如果未使用的数i比当前数大,则不处理,并且将vis设为-1,意为已经出现过一次了
if(a[j]<i) vis[a[j]]=-1;
}
j++;
}
}
}
printf("%d\n",ans);
printf("%d",a[0]);
for(i=1;i<n;i++) printf(" %d",a[i]);
printf("\n");
}
return 0;
}