末日传说

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;
}

猜你喜欢

转载自www.cnblogs.com/qswx/p/9644077.html