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;
}
posted @ 2025-10-09 21:21  michaele  阅读(10)  评论(0)    收藏  举报