题目链接:
B1难度 http://codeforces.com/contest/513/problem/B1 (n <= 8)
B2难度 http://codeforces.com/contest/513/problem/B2 (n <= 50)
题目大意:
对于一个排列p个定了函数f(p)的定义, 为所有的1 <= l <= r <= n的数对[l, r]中 min(a[l], a[l + 1], ... a[r]) 的和(一共是n*(n + 1)/2个数的和)
现在对于n个数的排列, 输出在满足f(p)最大的排列中字典序从小到大排在第m个的排列
例如n = 3的时候, 所有f(p)最大的排列有:
1, 2, 3
1, 3, 2
2, 3, 1
3, 2, 1
一共4种, 他们的f(p)都是1 + 2 + 3 + 2 + 2 + 1 = 11
当m = 2时输出第二个也就是1, 3, 2
大致思路:
想了好久的策略题, 终于还是想出来了, 首先不难发现小的数要尽量往左边或者右边放, 一次放置1, 2,.. n
放置1的时候可以是最左边或者最右边, 然后放置2, 放在剩下位置的最左边或者最右边, 3同理
而且不难发现n个数的排列当中满足f(p)最大的刚好有2^(n - 1)种(不难用动态规划证明)
这里给出4的所有可能:
1, 2, 3, 4
1, 2, 4, 3
1, 3, 4, 2
1, 4, 3, 2
2, 3, 4, 1
2, 4, 3, 1
3, 4, 2, 1
4, 3, 2, 1
而每一次决定放置数字i的时候放在左边和放在右边的字典序不一样, 刚好评分剩余的2^(n - 1)/ (2^i)中情况
所以讨论一下剩余的m与每次放在左边时的剩余的字典序的个数来判断放在左边还是右边即可
代码如下:
B1难度 Result : Accepted Memory : 0 KB Time : 15 ms
B2难度 Result : Accepted Memory : 0 KB Time : 31 ms
/* * Author: Gatevin * Created Time: 2015/2/25 15:55:43 * File Name: poi~.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; int main() { /* for(int i = 1; i <= 4; i++) { for(int j = 1; j <= i; j++) cout<<dp[i][j] << " "; cout<<endl; } */ int n; lint m; scanf("%d %I64d", &n, &m); if(n == 1) { printf("1\n"); return 0; } int a[110]; lint all = (1LL << (n - 1)); int fir = 1, end = n; for(int i = 1; i <= n; i++) { if(m > (all >> 1)) { a[end] = i; end--; m -= (all >> 1); all >>= 1; continue; } a[fir] = i; fir++; all >>= 1; } for(int i = 1; i <= n; i++) printf("%d ", a[i]); return 0; }