题目描述

只要是参加jsoi活动的同学一定都听说过Hanoi塔的传说:三根柱子上的金片每天被移动一次,当所有的金片都被移完之后,世界末日也就随之降临了。

在古老东方的幻想乡,人们都采用一种奇特的方式记录日期:他们用一些特殊的符号来表示从1开始的连续整数,1表示最小而N表示最大。创世纪的第一天,日历就被赋予了生命,它自动地开始计数,就像排列不断地增加。

我们用1-N来表示日历的元素,第一天日历就是

1, 2, 3, … N

第二天,日历自动变为

1, 2, 3, … N, N-1

……每次它都生成一个以前未出现过的“最小”的排列——把它转为N+1进制后数的数值最小。

日子一天一天地过着。有一天,一位预言者出现了——他预言道,当这个日历到达某个上帝安排的时刻,这个世界就会崩溃……他还预言到,假如某一个日期的逆序达到一个值M的时候,世界末日就要降临。

什么是逆序?日历中的两个不同符号,假如排在前面的那个比排在后面的那个更大,就是一个逆序,一个日期的逆序总数达到M后,末日就要降临,人们都期待一个贤者,能够预见那一天,到底将在什么时候到来?

解析

发现窝模拟有大问题,这道题就是个简单的贪心,结果窝思路清晰依然写不出。。。

题目要求我们输出一个长度为\(n\)的含有\(m\)个逆序对的字典序最小的序列。

众所周知,一个长度为\(n\)的序列最多贡献\(n(n-1)/2\)个逆序对,因此当我们把某个数\(i\)放到序列末尾时,除了之前在这个数后面的数会与它形成逆序对之外,还会最多产生\((n-i)(n-i-1)/2\)个逆序对。我们只需贪心地每次选择最靠后的能使产生的逆序对小于\(m\)个数放到序列末尾,就可以产生一个符合要求的序列。

一开始本来想写递归的,发现我太蒻,竟然不会写。。。(太菜了

参考代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define ll long long
#define N 50010
using namespace std;
ll n,m,ans[N];
int main()
{
scanf("%lld%lld",&n,&m);
ll s=1,e=n;
for(int i=1;i<=n;++i){
ll t=(ll)(n-i)*(n-i-1)/2;
if(t>=m) ans[s++]=i;
else ans[e--]=i,m-=(e-s+1);
}
for(int i=1;i<=n;++i) printf("%lld ",ans[i]);
return 0;
}
05-28 08:08