P1338 末日的传说
作者:互联网
https://www.luogu.com.cn/problem/P1338
贪心,逆序对
黄色题
思路:
贪心,逆序对
黄色题
思路:
我们考虑把这个问题缩小范围。
比如n=5,在决定了最小的数“1”的位置之后,剩下的几个数是2 3 4 5,但是他们
具体是多少没必要关心,我们只要关心他们的相对大小关系。
所以考虑完当前最小的数,算出这个数对答案的贡献,然后减掉这个贡献,
就可以转而解决一个更小的子问题。(即n-->n-1)
回到题目上,要求是求一个有m个逆序对的字典序最小的排列。
我们知道一个长度为n的排列最多有(n-1)*n/2个逆序对,也知道一个排列的逆序对数越多,排列字典序越大。
所以如果当前m不比当前的(n-2)*(n-1)/2(也就是减少一个数之后的最多的逆序对数)大,
就可以直接把当前的最小数放在最前面,这肯定是最优的。
反之,则考虑最小数的放置位置。
假设当前排列长为n,最小数为a,则a有n种放法,放在从左到右第i个位置时会生成i-1个逆序对
(因为它左边有i-1个比他大)。
因为m大于n-1长度排列最多所能产生的逆序数,所以a不可能放在最前面,否则不满足条件。
怎么办呢?想到之前说的逆序对越多字典序越大,我们就必须让剩下的数能构成的逆序对数尽量小,所以a要放到最后,这样m减少的最多。
放完了a,问题就变成了n-1和m-(a的贡献)的子问题,递归求解即可。时间复杂度O(n)。
typedef long long ll; ll n,m,a[50005]; int main(){ scanf("%lld%lld\n",&n,&m); ll lst=n,fst=1; for(int i=1;i<=n;i++){ ll t=(ll)(n-i)*(n-i-1)/2; if(t>=m)a[fst++]=i;//放头上 else a[lst--]=i,m-=(lst-fst+1);//放最后 } for(int i=1;i<=n;i++)printf("%d ",a[i]); return 0; }
标签:排列,末日,int,ll,最小,lst,P1338,传说,逆序 来源: https://www.cnblogs.com/2elaina/p/16560488.html