P1338末日的传说

####

末日的传说

  • 题目
    P1338末日的传说
  • 题意
    构造一个字典序最小的逆序对数 M M 的序列
  • 分析
    a n a_n 是一个序列, f ( a n ) f(a_n) 是它的逆序对数,则满足:
    0 f ( a n ) ( n 1 ) n 2 0{\leq}f(a_n){\leq}\frac{(n-1)*n}{2}
    要让字典序最小,可以不动前面的序列,在后面的序列中重排使得逆序对数等于题目要求的 M M 。需要的后面的序列的个数是 i i (满足不等式 ( i 1 ) i 2 M \frac{(i-1)*i}{2}{\geq}M ,最小的 i i ),那么就可以分成三部分去构造。
    • 第一部分 升序排列(对 M M 没有贡献,使得字典序尽量小).
    • 第二部分 前面的i形成的逆序对数是比 M M 要大,要构成 M M ,需要把 i i 中的第 m ( i 1 ) i 2 m-\frac{(i-1)*i}{2} 个数放到这里.那么逆序对数就会减少到 M M .
    • 第三部分 降序排列(对 M M 所有的贡献).
      构造样例:
      5 5 4 4
      12345 1 2 3 4 5
      根据上述的方法:
    • 先算出需要多少个数贡献逆序对数,算出是 4 3 2 4 \frac{4*3}{2}{\geq}4 ,所以 i i 算出是 4 4 。序列是: 1 5432 1|5432
    • 此时贡献的逆序对数是 6 6 ,与要求不符。找到原序列 i 2345 i中的2345 第二个数放到第二部分中,就变成了 13542 13542 , 5 5 4 4 因为 3 3 的提前逆序对数均少了 1 1 ,总的就减少了 2 2 。取 3 3 放前面是因为要使得字典序最小,同时保证答案的正确。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main ()
{
    ll n,m;
    cin>>n>>m;
    if(m == 0)
    {
    	for(int i= 1;i<=n;i++)
    		cout<<i<<" ";
    	return 0;
    }
    for(ll i= 2;i <= n;i++)
    {
       if(i*(i-1)/2 >= m && (i-1)*(i-2)/2 < m)//找到第一个i的值同时后面部分(i-1)的逆序对不要超过m
       {
           int _ = i;//需要变成降序的个数,放到第三部分
           int __ = i*(i-1)/2-m;//找到第_个值放到第二部分
           for(int i =1;i<=n - _;i++)
           	cout<<i<<" ";//输出第一部分
           cout<<n-__<<" ";//输出第二部分
           for(int i= n;i>=(n-_+1);i--)
           {
           	if(i != n-__)//注意不要把第二部分输出了
           		cout<<i<<" ";//降序输出第三部分
           }
           return 0;
       }
    }
	return 0 ;
}

猜你喜欢

转载自blog.csdn.net/strategist_614/article/details/86517153