题意:在从小到大产生全排列时,最先输出逆序对为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; }