CodeForces 513B1 Permutations

B1. Permutations
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

You are given a permutation p of numbers 1, 2, ..., n. Let's define f(p) as the following sum:

Find the lexicographically m-th permutation of length n in the set of permutations having the maximum possible value of f(p).

Input

The single line of input contains two integers n and m (1 ≤ m ≤ cntn), where cntn is the number of permutations of length n with maximum possible value of f(p).

The problem consists of two subproblems. The subproblems have different constraints on the input. You will get some score for the correct submission of the subproblem. The description of the subproblems follows.

  • In subproblem B1 (3 points), the constraint 1 ≤ n ≤ 8 will hold. 
  • In subproblem B2 (4 points), the constraint 1 ≤ n ≤ 50 will hold. 
Output

Output n number forming the required permutation.

Sample test(s)
input
2 2
output
2 1 
input
3 2
output
1 3 2 
Note

In the first example, both permutations of numbers {1, 2} yield maximum possible f(p) which is equal to 4. Among them, (2, 1) comes second in lexicographical order.


这道题是寒假集训做到的最有意思的题目(明明就没做几道题啊喂)

题目大意是求一个排列p,满足使函数f(p)达到最大值

而这样的p可能有多个,于是题目又给了m,求在满足条件的这些排列中按字典序排第m个的排列

一开始傻逼一样地用STL的next_permutation暴力,大概数据给的比较小,竟然AC了(⊙o⊙)…

#include<iostream>
#include<cmath>
#include<utility>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn = 1000000000;

int main() {
  int n, m, per_cnt = 0, per_max = -maxn;
  cin >> n >> m;
  int a[n];
  for (int i = 0; i < n; i++)
    a[i] = i+1;
  do {
    int temp_max = 0;
    for (int i = 0; i < n; i++) {
      int temp = maxn, min_sum = 0;
      for (int j = i; j < n; j++) {
        if (a[j] < temp) temp = a[j];//按照f(p)的表达式求函数值
        min_sum += temp;
      }
      temp_max += min_sum;
    }
    
    if (temp_max < per_max) continue;
    if (temp_max > per_max) { per_cnt = 0; per_max = temp_max; }
    if (temp_max == per_max) {
      per_cnt++;
      if (per_cnt == m) {
        int first = 1;
        for (int k = 0; k < n; k++) {
          if (first) first = 0;
          else cout << " ";
          cout << a[k];
        }
        cout << endl;
      }
    }
  } while (next_permutation(a, a+n));
  return 0;
}

后来想想这样貌似不对,此时per_max可能还不是函数f(p)的最大值

暂时还没想到相应数据卡这种做法


然后晚上上去讲这题果然被学长鄙视了

随后就被碾压了

下面讲讲碾压的全过程╮(╯▽╰)╭


由函数f(p)的表达式可以看出

当较小的数放在越中间时,其在 p(i),p(i+1),p(i+2),...,p(j) 中为最小数的次数越多

这也就意味着f(p)的值越小

为了使f(p)达到最大值,那么最小的数应放在排列p的最左或最右

类似地,第二小的数应该放在排列p的正数第二个位置或倒数第二个位置

以此类推


下面考虑排列p和其逆序p',分析函数f(p)的特征可知,f(p) = f(p')

因此,当我们按照上面讲的方法构造的就是使f(p)达到最大值的排列p的集合

由排列组合的知识知这样的p一共有2^(n-1)个

那么所有满足题意的排列p都求出来了,怎么求按字典序的第m个捏


由字典序的定义可以知道,如果将所有满足条件的排列p按字典序排列,记此时已排序的集合为A

那么前一半排列p的1一定在第一位,后一半排列p的1一定在最后一位

类似地,在前一半的排列p中,前一半排列p的2一定在正数第二位,后一半排列p的2一定在倒数第二位

也就是形如 (1,2,...) 的排列p一定在集合A的前1/4,形如 (1,...,2)的排列p一定在集合A的前1/4到1/2之间

程序的实现就比较简单了:不断地二分直至构造出第m个排列


代码如下

#include<iostream>
using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  int l_iter = 0, r_iter = n-1, sum = 0, cnt = 0, per[n];
  for (int i = n-2; i >= 0; i--)
    if (sum <= m && m <= sum+(1<<i)) { per[l_iter++] = ++cnt; }
    else {
      per[r_iter--] = ++cnt;
      sum += (1<<i);
    }
  per[r_iter--] = ++cnt;
  for (int i = 0; i < n; i++)
    if (i == 0) cout << per[i];
    else cout << " " << per[i];
  cout << endl;
  return 0;
}

学长讲完后发现这好像是道前几年的数学竞赛题>_<|||

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值