42 P7690 [CEOI 2002] A decorative fence 题解
[CEOI 2002] A decorative fence
题面
有 \(N\) 块长度为 \(1 ... N\) 的木板,宽度都是 1,现在要用这 \(N\) 块木板组成一个宽度为 \(N\) 的栅栏,满足在栅栏中,每块木板两侧的木板要么比它高要么比它低。也就是说,栅栏中的木板是高低交错的。我们称“两侧比它低的木板”处于高位,“两侧比他高的木板处于高位”
给定 \(N,C\) 求出从左到右各个木板的长度,\(C\) 为这种方案在所有合法方案中的字典序排名
\(1 \le N \le 20,\ 0 < C < 2 ^ {63}\)
题解
我们可以用试填法来确定各个木板的长度,具体来说
从小到大枚举第 \(i\) 块木板长度 \(len\) ,然后看后面 \(N - i\) 块木板的方案数 \(T\)
若 \(C \le T\) 那么说明第 \(i\) 块木板用 \(len\) 长度的木板是可以的
否则说明我们要换用更长的木板,将 \(C\) 减去 \(T\) ,然后继续上面的过程
为了方便计算,我们要预处理出一个 \(f(i,j,k)\) 表示共 \(i\) 块长度不同的木板,最左边一块的长度在这 \(i\) 块木板中的排名为 \(j\) ,并且其处于 \(k\) 位(0 表示低位,1 表示高位)
\[\begin{align}
f(i,j,0) = \sum_{p = j}^{i - 1} f(i - 1, p, 1) \\
f(i,j,1) = \sum_{p = 1}^{j - 1} f(i - 1, p, 0)
\end{align}
\]
时间复杂度为 \(O(n^3)\)
这种试填法实际上是在一步一步缩小查找范围,直到找到目标状态
每次减去一个方案数,实际上是排除了那个分类
注意一些细节,比如第一个数可以是高位也可以是低位,所以要讨论一下
其他位置可以由前面的高低判断当前的高低
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 25;
int T, n;
ll f[N][N][2], C;
bool vis[N];
void prework () {
f[1][1][0] = 1;
f[1][1][1] = 1;
for (int i = 2; i <= 20; i ++) {
for (int j = 1; j <= i; j ++) {
for (int k = 1; k <= j - 1; k ++) {
f[i][j][1] += f[i - 1][k][0];
}
for (int k = j; k <= i - 1; k ++) {
f[i][j][0] += f[i - 1][k][1];
}
}
}
}
int main () {
prework ();
cin >> T;
while (T --) {
memset (vis, 0, sizeof vis);
cin >> n >> C;
int la = 0, k = 0;
for (int i = 1; i <= n; i ++) {
if (C <= f[n][i][1]) {
la = i;
k = 1;
break;
} else C -= f[n][i][1];
if (C <= f[n][i][0]) {
la = i;
k = 0;
break;
} else C -= f[n][i][0];
}
vis[la] = 1;
printf ("%d ", la);
for (int i = 2; i <= n; i ++) {
k ^= 1;
int cnt = 0;
for (int j = 1; j <= n; j ++) {
if (vis[j]) continue;
//cnt 用来记录当前 j 在没用过的数中的排名
cnt ++;
if ((k == 0 && j < la) || (k == 1 && j > la)) {
if (C <= f[n - i + 1][cnt][k]) {
la = j;
vis[la] = 1;
break;
} else C -= f[n - i + 1][cnt][k];
}
}
printf ("%d ", la);
}
printf ("\n");
}
return 0;
}

浙公网安备 33010602011771号