1067 Sort with Swap(0, i) (25 分)

题意

给出\(0, 1,\cdots ,N-1\)的一个序列,要求通过两两交换的方式将其变为递增序列,但是规定每次只能用0与其他数进行交换。求最小交换次数。

思路

由于必须使用数字0跟其他数进行交换,因此直观上可以很容易想到的策略是:如果数字0当前在i号位,则找到数字i当前所处的位置,然后把0与i进行交换。该策略希望通过重复这个操作,直到序列变为有序。

但是由此发现,这个策略并不能完全解决问题,因为一旦在交换过程中(或是初始序列时)数字0回到了0号位,按上面的算法就不能将其与一个确定的数交换,这时就要想办法处理这种情况。通过思考发现,通过该策略,一旦一个非零的数字回到了它原先的位置,在后面的步骤中就不应当再让0去与它交换,否则会让交换次数变多。也就是说,非零的数字在回到其“本位”后将不再变动。

这就得到一个启示:如果在交换过程中出现数字0在0号位的情况,就随意选择一个还没有回到“本位”的数字,让其与数字0交换位置。显然,这种交换不会对已经在“本位”的数字产生影响,但却使得上面的策略得以继续执行。

这个策略的证明很直观。由于0必须参加交换操作,因此通过该策略,每步总是可以将一个非零的数回归本位。如果用0与其他不是该位置编号的数进行交换,显然会产生一个无效操作,因为后续操作中还是需要将刚才交换的数换回本位,因此该策略能将无效操作次数(与0交换的数没有回归本位的次数)降到最小,于是最优。

注意点

在循环中寻找一个不在本位上的数时,如果每次都从头开始枚举序列中的数,判断其是否在其本位上,则会有两组数据超时(因为复杂度是二次方级的)。更合适的做法是利用每个移回本位的数在后续操作中不再移动的特点,从整体上定义一个变量left,用来保存目前序列中不在本位上的最小数(初始为1),当交换过程中出现0回归本位的情况时,总是从当前的left开始继续增大寻找不在本位上的数,这样就能保证复杂度从整体上是线性级别(left在整个算法过程中最多只会从0增长到n)。

const int N=1e5+10;
int a[N];
int pos[N];
int n;

int main()
{
    cin>>n;

    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        pos[a[i]]=i;
    }

    int cnt=0;
    int left=0;
    while(true)
    {
        if(pos[0] == 0)
        {
            bool ok=true;
            for(int i=left;i<n;i++)
            {
                left++;
                if(pos[i] != i)
                {
                    ok=false;
                    swap(pos[0],pos[i]);
                    cnt++;
                    break;
                }
            }

            if(ok) break;
        }

        int index=pos[0];
        swap(pos[0],pos[index]);
        cnt++;

    }
    cout<<cnt<<endl;
    //system("pause");
    return 0;
}
posted @ 2021-02-19 10:24  Dazzling!  阅读(59)  评论(0编辑  收藏  举报