题目大意:
一个整数序列a1, a2, … , an的倒置数是数对(ai, aj)满足i < j并且ai > aj.给定n和倒置数m,你的任务是找到集合最小的序列{1,2,…,m},倒置数正好为m。
解题思路:
仔细观察下面的几组数据,每行分别表示invertion number和对应的一组最小的排列:
1: 2 1
2: 2 3 1
3: 3 2 1
4: 2 4 3 1
5: 3 4 2 1
6: 4 3 2 1
7: 2 5 4 3 1
8: 3 5 4 2 1
9: 4 5 3 2 1
10: 5 4 3 2 1
11: 2 6 5 4 3 1
12: 3 6 5 4 2 1
13: 4 6 5 3 2 1
14: 5 6 4 3 2 1
15: 6 5 4 3 2 1
相信很容易就看出了规律吧。首先,相同长度之间排列的规律,其次就是每组长度相同的数据中最后一个invertion number与长度的关系。哈哈,很简单吧。然后根据n,把前面的几个数从小到大输出来,然后将对应的invertion number的排列数的每个数加上前面已经输出的数的个数t,就是所求答案了。
当然了,记录这些排列显然内存是不够的,而且n的最大值为50000,是在太大,这时候,相同长度之间的规律就派上用场了。
我们只需要用一个数组list,以长度为下标,每组最后一个invertion number存起来,list[2]=1, list[3]=3,
list[4]=6, list[5]=10, list[6]=15, …时间复杂度O(n)
这样,给定m后,就可以通过二分查找,用O(logn)的时间找到对应的长度。然后根据所找到的规律,将结果打印出来,时间复杂度O(n)
这样,总的时间复杂度为O(n)。
代码如下:
#include<iostream>
using namespace std;
int n,m, id;
int list[50001];
int find(int num)
{
int L=1, R=n, mid;
while(L<R-1)
{
mid=L+R>>1;
if(list[mid]>= num) R=mid;
else L=mid;
}
return L;
}
int main()
{
for(int i=1; i<50000; ++i)
list[i+1]=list[i] + i;
while(1)
{
cin>>n>>m;
if(n==-1 && m==-1) break;
if(m==0)
{
cout<<1;
for(int i=2; i<=n; ++i)
cout<<' '<<i;
cout<<endl;
}
else
{
id=find(m);
int end=n-id;
int num;
for(int i=1; i<end; ++i)
cout<<i<<' ';
num=m-list[id];
cout<<(num+end);
for(int i=id; i>=0 ;--i)
if(num!=i)
cout<<' '<<(i+end);
cout<<endl;
}
}
return 0;
}