1 条题解

  • 0
    @ 2025-8-24 21:23:57

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar zqy1018
    **

    搬运于2025-08-24 21:23:56,当前版本为作者最后更新于2017-06-17 22:54:29,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    我们考虑把这个问题缩小范围。

    比如n=5,在决定了最小的数“1”的位置之后,剩下的几个数是2 3 4 5,但是他们

    具体是多少没必要关心,我们只要关心他们的相对大小关系

    所以考虑完当前最小的数,算出这个数对答案的贡献,然后减掉这个贡献,

    就可以转而解决一个更小的子问题。(即n-->n-1)

    回到题目上,要求是求一个有m个逆序对的字典序最小的排列。

    我们知道一个长度为n的排列最多有(n-1)*n/2个逆序对,也知道一个排列的逆序对数越多,排列字典序越大。

    所以如果当前m不比当前的(n-2)*(n-1)/2(也就是减少一个数之后的最多的逆序对数)大,

    就可以直接把当前的最小数放在最前面,这肯定是最优的。

    反之,则考虑最小数的放置位置。

    假设当前排列长为n,最小数为a,则a有n种放法,放在从左到右第i个位置时会生成i-1个逆序对

    (因为它左边有i-1个比他大)。

    因为m大于n-1长度排列最多所能产生的逆序数,所以a不可能放在最前面,否则不满足条件。

    怎么办呢?想到之前说的逆序对越多字典序越大,我们就必须让剩下的数能构成的逆序对数尽量小,所以a要放到最后,这样m减少的最多。

    放完了a,问题就变成了n-1和m-(a的贡献)的子问题,递归求解即可。时间复杂度O(n)。

    typedef long long ll;
    ll n,m,a[50005];
    int main(){
        scanf("%lld%lld\n",&n,&m);
        ll lst=n,fst=1;
        for(int i=1;i<=n;i++){
            ll t=(ll)(n-i)*(n-i-1)/2;
            if(t>=m)a[fst++]=i;//放头上
            else a[lst--]=i,m-=(lst-fst+1);//放最后
        }
        for(int i=1;i<=n;i++)printf("%d ",a[i]);
        return 0;
    }
    
    • 1

    信息

    ID
    336
    时间
    1000ms
    内存
    125MiB
    难度
    3
    标签
    递交数
    0
    已通过
    0
    上传者