Restore the Permutation ——Codefroces

目录

题目:

​编辑 解题思路:

AC代码


题目:

 

 
解题思路:

         同大部分的解题思路一样,我们采用贪心算法,对于每一个数,我们给它剩下还可用的数字中的比它小的数字的最大值。但是在这里处理的时候,我们要从后往前处理。让我来简单说说这种贪心的可行性。

        那就构造一个一组数据数据说吧,假设给定n=6, 数字序列为3 2 6。 因为我们需要构造出字典序最小的组合,所有我们要尽可能地把大的数字排在后面, 那这样的话我们是不是可以直接从前面开始,每次都分配可用数字种最小的那个数不就好了吗。可事实这样是不行,在3 2 6中,按上面的想法,把1给了3,但是在2的时候就只能选4和5,显然,这样并不符合题意,因为4,5均大于3。

        重新再回到我们的可行的贪心算法中, 我们从后往前进行,每次分配给当前数字的都是可选数字中的比它小的数子中的最大值。那为什么每次给的都是最大值呢?这当然是为了构造出字典序最小的组合了。如6,剩下可选的数字是1 4 5,那就把5给它,来到2 ,剩下的是1 4,那就只能给它1,把4 给3,那就是3 4 1 2 5 6 这个序列了,这就是我们的答案。为什么这样可行使我们最关注的问题,我就简单地说说我的想法吧,当我们用掉一个数字后,我们最关心的就是会不会影响到前面的数字,导致前面的某个数字没有数字可以分配这种情况的出现(就像上面我们说的把1给3,导致2无数字可用)。这里我们重新构造一组数据,给定n=6,数字序列为6 2 4,现在考虑最后一个数字4,可选的数字有1和3,选取较大的数字3,4后面(这里4后面没有)的已经确定,我们只需要考虑会不会影响到在4前面的数字,在4前面的有比4小的数字2,也有比4大的数字6。对于比4大的数字,我们选取任何可选数字都不会出现它无数字可选的情况出现,因为我们选取的数字是比4小的,那也一定会比6小。但是对与前面比4小的数字2,可选数字1比2小,3比2大,就会出现这两个情况。选1给4后,就会导致2无数字可选了。但是我们的策略中每次都是选取的最大值,不论怎样我么都是要给4一个数字的,选取第二大的可能就导致第一大的数字在前面无法分配,但是选取最大的一定不会造成这样的影响(除非本组数据本就是无解的)。这样一定不会影响到前面2的选择,同时还满足了最小字典序的要求。

        好,简单看完了上面的,那我们的再简单说说什么情况下数据无解,像这样n=6,数字序列1 2 3这样显然无解,还有就是有重复数字。在代码中我们处理无解的情况也非常简单,我们处理到一个数字无数字可选的时候,就会返回最大值是0,代表无数字可选,另外,针对重复数字,那我们在最后一定还会剩下可选数字,也就是说,剩余数字的最大值不是0。这些在线段树中都非常好处理。

AC代码

        

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<numeric>

using namespace std;

//贪心+线段树维护区间最大值,时间复杂度o(2n-1*log(2n-1)),主要时间在建立线段树上

int dp[400010];  //线段树维护一个区间最大值

int Tree[800010]={0};

void BuildTree(int left,int right,int index)  //建线段树
{
    if(left>right) return;
    if(left==right)
    {
        Tree[index]=dp[left];
        return;
    }

    int mid=(left+right)>>1;

    BuildTree(left,mid,index*2);
    BuildTree(mid+1,right,index*2+1);

    Tree[index]=max(Tree[index*2],Tree[index*2+1]);
}

int findans(int L,int R,int left,int right,int index)  //在left到right区间里寻找区间[L,R]的最大值
{
    if(R<left||L>right) return 0;

    if(left>right) return 0;

    if(left>=L&&right<=R) return Tree[index];

    int ans1=0,ans2=0;

    int mid=(left+right)>>1;

    ans1=findans(L,R,left,mid,index*2);
    ans2=findans(L,R,mid+1,right,index*2+1);

    return max(ans1,ans2);

}

//修改,将线段树叶子值为target的节点修改为0,表示已经使用过该数字,递归更新区间最大值
void modify(int left,int right,int target,int index)
{
    if(left>right) return;
    if(left==right&&Tree[index]==target)
    {
        Tree[index]=0;
        return;
    }

    int mid=(left+right)>>1;

    if(target<=mid) modify(left,mid,target,index*2);
    if(target>mid) modify(mid+1,right,target,index*2+1);

    Tree[index]=max(Tree[index*2],Tree[index*2+1]);

}

int main()
{
    int t,n,a;
    cin>>t;
    int nums[200010],ans[200010];
    while(t--)
    {
        scanf("%d",&n);

        iota(dp+1,dp+n+1,1);  //填充数字1....n

        for(int i=1;i<=n/2;i++)
        {
            scanf("%d",nums+i);
            dp[nums[i]]=0;  //将nums[i]这个数字记成0,区间最大值中就不会出现nums[i],表示不可用
        }

        BuildTree(1,n,1);  //建树

        bool flag=true;
        for(int i=n/2;i>=1;i--)
        {
            ans[i]=findans(1,nums[i]-1,1,n,1); //寻找比它小的最大值
            if(ans[i]==0)  //返回0表示没有可用的数字,那么就没有这样一个组合
            {
                flag=false;
                break;
            }

            modify(1,n,ans[i],1); //将得到的最大值使用后标记为0,后序就用不了了
        }

        if(flag==false||Tree[1]!=0)
        {
            cout<<-1<<endl;
            continue;
        }

        for(int i=1;i<=n/2;i++)
        {
            if(nums[i]<ans[i])
            {
                cout<<nums[i]<<" "<<ans[i]<<" ";
            }
            else
            {
                cout<<ans[i]<<" "<<nums[i]<<" ";
            }
        }
        cout<<endl;

    }

    return 0;
}

感谢您的观看。

猜你喜欢

转载自blog.csdn.net/qq_65120254/article/details/128400082
今日推荐