P1338 末日的传说

 

 首先审题,题目中提到    每次它都生成一个以前未出现过的“最小”的排列。其实这句话的意思就是说,我这个日期每天都会变化,每次变化后的排列都是以前没出现过的,也就是不会有重复的排列,然后这个日期还是除了以前出现过的排列之外的最小的排列,也就是说啊,我这个日期是按照一定规律来变化的,第一天是字典序最小,第二天是字典序第二小,,,就这样一直排列,最后一天就是字典序最大,也就是倒序排列,

然后题目中要求的是:如果这个日历的变化过程中,有一个排列的逆序总数达到某个特定的值的话,就会世界末日,我们要求世界末日的日期,也就是求世界末日时这个日历的排列。

那么这个排列会满足两点:

1、这个排列应该是尽可能字典序小的一个排列,因为这个日历的变化规律是从字典序小到字典序大,中间有世界末日的话,就没后边字典序大的什么事了,所以我们应该让这个排序尽可能小。

2、这个排列的逆序总数是题目中要求m,这里说下逆序总数的意思:就是这一个排列里,前边的数比后边的数大的话,就是一个逆序,这两个数就形成了一个组合,我们叫逆序对。逆序总数就是在这一个排列中,总共有多少逆序对。

比如说:

1  2  3   逆序总数为0     因为这是顺序排列,每一个数都小于它后边的,所以没有逆序对。

1  3  2   逆序总数为1     因为3大于2,只有这一个逆序对,所以逆序总数为1

3  2  1   逆序总数为3     因为3大于2,3大于1,2大于1,有三个逆序对,所以逆序总数为3

 

那么接下来我们就要找这个一个排列,他要尽可能字典序小的同时,满足逆序总数的要求

首先呢,暴力是肯定不行的,如果你遍历每一个排列,并且查找每一个排列的逆序数,这里数据最多10的四次方,肯定会超时。

我们要知道:一个长度为n的排列的逆序对的最大值为n*(n-1)/2,题目中给的m肯定不会大于这个值,

  这道题的想法呢,就是一步一步的走,然后每一步都走最优的,等到最后就是最优解了。

一共有n个数的排列,我们就一个一个的排,每排一个都让它最优

我们从排列里一个一个的拿,顺序是从小到大的拿,拿完一个拿下一个,也就是说每次我拿的数,都是剩下没有排列的数中最小的,现在假设我们拿的这个数是i(i从1开始),我们拿走它,然后这个排列的长度就变成n-i了,长度为n-i的排列的逆序对的最大值是(n-i)*(n-i-1)/2,这个时候我们拿它和m比,

如果m小于等于这个逆序对的最大值,说明什么呢,说明我这个最小的数不用管逆序对的事,因为我这剩下的n-i个数都可以产生大于等于m的逆序对数,所以我就让这n-i个数来管逆序对的事就行了,我这个最小的数不管那些,那最小的数管哪些呢,我们前边提到我们要让这个排列尽可能的字典序小,那什么情况下字典序小呢,当然就是高位的数越小,字典序越小,所以我们就把这个最小的数放在排列第一位,这样就能让这个排列的字典序尽可能的小,是最优的!

如果m大于这个逆序对的最大值,那么就说明,这个最小的数必须要管逆序对的事了,因为这剩下的n-i个数最多最对能产生的逆序对数也不够m用,所以这个时候必须要加上这个最小的数的贡献才行,那么这个时候这个最小的数放在哪里是最优的呢,放在最后是最优的,因为这个时候,逆序对数不够用了,所以我们应该尽可能的产生更多的逆序对才好,如果我们把这个最小的数放在排列的最后面的话,那么剩下的所有未排列的数都会与这个最小的数产生一个逆序对,因为我们是按照大小顺序来拿的,先拿的都会小于没有排列的数,剩下的所有未排列的数都大于这个最小的数。这时,逆序对数就增加了n-i,这时候m就应该减去增加的逆序对数,我们要让m显示的是目前为止还差多少逆序对数,这样方便后面不停的迭代判断。每一步就是判断剩下的数最多能产生的逆序对数的值和目前为止还差多少逆序对数的m值相比。如果这个值足够m用,我们就把当前拿的这个数尽量放在排列前,这样能让排列的字典序尽可能小,如果不够m用,我们就把当前拿的这个数放尽量放在排列后,这样就能让排列产生更多的逆序对。

然后就这样一直循环就行了,

最后上代码,代码很少,,,,,,,

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 int a[50005];
 5 int main(){
 6     long long n,m;
 7     scanf("%lld%lld",&n,&m);
 8     int fst=1,lst=n;
 9     for(int i=1;i<=n;i++){
10         long long t=(n-i)*(n-i-1)/2;
11         if(m<=t){
12             a[fst++]=i; 
13         } 
14         else{
15             a[lst--]=i;
16             m-=lst-fst+1; 
17         }
18     }
19     for(int i=1;i<=n;i++){
20         printf("%d ",a[i]);
21     }
22     return 0;
23 }

这个思路的作者是洛谷的 zqy1018,我是看了它的题解明白了之后,才写的这篇博客,感谢。

原文地址:https://www.cnblogs.com/fate-/p/12814026.html