zoukankan      html  css  js  c++  java
  • P1338 末日的传说

    题目描述

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

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

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

    1, 2, 3, … N

    第二天,日历自动变为

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

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

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

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

    输入输出格式

    输入格式:

    只包含一行两个正整数,分别为N和M。

    输出格式:

    输出一行,为世界末日的日期,每个数字之间用一个空格隔开。

    输入输出样例

    输入样例#1:
    5 4
    
    输出样例#1:
    1 3 5 4 2
    

    说明

    对于10%的数据有N <= 10。

    对于40%的数据有N <= 1000。

    对于100%的数据有 N <= 50000。

    所有数据均有解。

    我们考虑把这个问题缩小范围。

    比如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)。

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define lli long long int 
    using namespace std;
    const lli MAXN=50001;
    inline void read(lli &n)
    {
    	char c='+';lli x=0;bool flag=0;
    	while(c<'0'||c>'9')
    	{c=getchar();if(c=='-')flag=1;}
    	while(c>='0'&&c<='9')
    	{x=(x<<1)+(x<<3)+c-48,c=getchar();}
    	flag==1?n=-x:n=x;
    }
    lli a[MAXN];
    lli ed,bg;
    int main()
    {
        lli n,m;
        read(n);read(m);
        ed=n;
        bg=1;
        for(lli i=1;i<=n;i++)
        {
        	lli num=(n-i)*(n-i-1)/2;
        	if(num>=m)
        		a[bg++]=i;
        	else a[ed--]=i,m-=(ed-bg+1);
    	}
    	for(lli i=1;i<=n;i++)
    		printf("%lld ",a[i]);
        return 0;
    }
    

      

  • 相关阅读:
    对数组对象处理及其他小问题
    前端面试题库
    题解 P3371 【【模板】单源最短路径】
    题解 P2403 【[DOI2010]所驼门王的宝藏】
    题解 P2283 【[HNOI2003]多边形】
    题解 P1074 【靶形数独 】
    题解 P1064 【金明的预算方案】
    题解 CH1813 【双栈排序】
    题解 CH1809 【匹配统计】
    题解 CH0805 【防线】
  • 原文地址:https://www.cnblogs.com/zwfymqz/p/7157753.html
Copyright © 2011-2022 走看看