洛谷P1338 末日的传说(贪心)

传送门

题意:在从小到大产生全排列时,最先输出逆序对为m的排列

首先,有一种情况最优,那就是前面的n-i个数正序,后面的i个数完全逆序,例如1 2 5 4 3,这样逆序的数量时i*(i-1)/2。

我们先确定m在i*(i+1)/2和(i+1)*(i+2)/2之间,先把这个i找到,这时已经有i*(i+1)/2个逆序,还差m-i*(i-1)/2个,如何构造这剩下的m-i*(i-1)/2个逆序呢。

重点来了,第n-i个数肯定比后面的i个数小,把第n-i个数和第n个数交换,后i个数的逆序还是i*(i-1)/2,但是第n-i和最后一个数形成一个逆序,得到的逆序数时1+i*(i-1)/2;

然后把交换完的第n-i个数跟倒数第二个数交换,这样第n-i个数比最后两个数大,逆序数时2+i*(i-1)/2

......

直到第n-i个数跟第n-i+1个数交换,这样第n-i个数比后i个数都大,逆序数时i+i*(i-1)/2=i*(i+1)/2,也就是后i+1个数完全逆序,前n-i-1个数完全正序。

这题还有一个坑点需要注意,就是逆序数m可能非常大,要开long long,这时m-i*(i-1)/2也要用long long存

后面i个数交换的环节中,最坏的情况是后i个数都与第n-i个数交换,复杂度是O(n),这么算不会超时

扫描二维码关注公众号,回复: 5003378 查看本文章
#include<iostream>
using namespace std;

typedef long long ll;
ll n,m;
int a[50005];

void init(int x)
{
    int nn=n-x;
    for(int i=1;i<=nn;i++)a[i]=i;
    int j=n;
    for(int i=nn+1;i<=n;i++)
    {
        a[i]=j;
        j--;
    }
}

void swap(int&x,int&y)
{
    ll t;
    t=x;
    x=y;
    y=t;
}

int main()
{
    cin>>n>>m;
    ll i;
    i=0;
    while(1)
    {
        if(m<(i+1)*i/2)break;
        i++;
    }
    i--;
    ll x=m-(i+1)*i/2;
    init(i+1);
    int nn=n-i-1;
    int j=n;
    for(i=1;i<=x;i++)
    {
        swap(a[nn],a[j]);
        j--;
    }
    for(i=1;i<=n;i++)cout<<a[i]<<" ";
    cout<<endl;
}

猜你喜欢

转载自www.cnblogs.com/lyhhahaha/p/10296878.html
今日推荐