zoj2425

来源:互联网 发布:淘宝商家投诉买家 编辑:程序博客网 时间:2024/05/21 12:41

题目大意:

一个整数序列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;}
0 0
原创粉丝点击