题目: https://www.luogu.org/problemnew/show/P1338
题解:
本题要用数学知识来做,如果用next_permutation来做,会严重超时。
正解:
如何让逆序对数为m的序列字典序最小呢? 假设位置p是第一个非原始序列的位置,那这个点应该尽量靠右,才能使得字典序最小。而为了保证有m个逆序对,要求p后面的逆序对数尽量大。怎么才能尽量大呢?当然是降序排列。这样问题就转成找点p,同时在找到p时还需要知道m-后面所有逆序对数剩余的值,这个值要在点p身上修改。则我们现在可以直接输出序列。
1)p之前的部分按照顺序输出
2)输出p,如果后面逆序对不够,则需要修改p点再输出。
3)逆序输出p之后的部分,注意如果p之前改点了,需要多判断一下。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
int p=n,c=0;//p代表第一个非原始序列的位置
for(int s=1;m>0;s++,p--)
{//找p的位置
c=min(s,m);//m代表一共还需要凑出多少逆序对 c代表本次p左移可以凑出多少逆序对
m-=c;//s代表p左移以为最多增加的逆序对个数
}
for(int i=1;i<p;i++){
cout<<i<<' ';
}//输出顺序
cout<<p+c<<' ';//输出第一个非原始序列位置上的数
//找到P的位置后,这个位置上的数每增加一逆序对就增加一
for(int i=n;i>=p;i--){
if(i!=p+c){
cout<<i<<' ';
}
}
return 0;
}
超时代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,m;
int a[50005];
cin>>n>>m;
for (register int i=1;i<=n;i++) a[i]=i;
while(1)
{
int sum=0;
for(register int i=1;i<=n-1;i++)
for(register int j=i+1;j<=n;j++)
if(a[i]>a[j]) sum++;
if(sum==m)
{
for(register int i=1;i<=n;i++) cout<<a[i]<<' ';
return 0;
}
next_permutation(a+1,a+1+n);
}
return 0;
}