末日传说

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;
}
原文地址:https://www.cnblogs.com/qswx/p/9644077.html