末日传说
Description
只要是参加jsoi活动的同学一定都听说过Hanoi塔的传说:三根柱子上的金片每天被移动一次,当所有的金片都被移完之后,世界末日也就随之降临了。
在古老东方的幻想乡,人们都采用一种奇特的方式记录日期:他们用一些特殊的符号来表示从1开始的连续整数,1表示最小而N表示最大。创世纪的第一天,日历就被赋予了生命,它自动地开始计数,就像排列不断地增加。
我们用1-N来表示日历的元素,第一天日历就是1, 2, 3, … N
第二天,日历自动变为1, 2, 3, … N, N-1
每次它都生成一个以前未出现过的“最小”的排列——把它转为N+1进制后数的数值最小。
日子一天一天地过着。有一天,一位预言者出现了——他预言道,当这个日历到达某个上帝安排的时刻,这个世界就会崩溃……他还预言到,假如某一个日期的逆序达到一个值M的时候,世界末日就要降临。
什么是逆序?日历中的两个不同符号,假如排在前面的那个比排在后面的那个更大,就是一个逆序,一个日期的逆序总数达到M后,末日就要降临,人们都期待一个贤者,能够预见那一天,到底将在什么时候到来?
Analysis
首先通过递推算出i个数字排列的最大逆序对数dp[i],很容易想到n个数M个逆序对的列首为M-dp[n-1]+1(当然要是M<=dp[n-1]就是1),接着递归产生子问题,只需要将每次算出的列首转换为真实值即可(即之前输出过的不能再输出)。
对于这唯一的难点,显然可以用树状数组。
Code
#include <bits/stdc++.h>
const int N=50000;
int dp[N+10];
class Binary_index_tree{
private:
int s[N+10];
int lowbit(int i){
return i&(-i);
}
public:
int n;
void Init(){
for(int i=1;i<=n;i++)
Modify(i);
}
void Modify(int i){
if(i>n)return;
s[i]+=1;
Modify(i+lowbit(i));
}
int Query(int i){
if(!i)return 0;
return s[i]+Query(i-lowbit(i));
}
}tree;
void pretreat(){
dp[1]=0;
for(int i=2;i<=N;i++)
dp[i]=dp[i-1]+i-1;
}
void search(int n,int m){
if(!n)return;
int k=m<=dp[n-1]?1:m-dp[n-1]+1;
printf("%d ",tree.Query(k));
tree.Modify(k);
search(n-1,std::min(dp[n-1],m));
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
pretreat();
tree.n=n;
tree.Init();
search(n,m);
return 0;
}