POJ1037 -----A decorative fence

来源:互联网 发布:砧板 知乎 编辑:程序博客网 时间:2024/05/21 00:18

题目链接: A decorative fence

题意:考虑[1, 2, ..., n]的n个数的排列a1, a2, a3, ..., an. 定义合法的排列为满足: 对任意的ai, a(i-1) > ai && a(i + 1) > ai 或者a(i - 1) < ai && a(i + 1) < ai. 对于所有的合法排列, 定义顺序关系为字典序,即: 排列p1={a1, a2, .., ai, .., an}, p2 = {b1, b2, .., bi, ... bn}. 若存在1 <= i <= n使得任意的1<= k < i, ak == bk, 且 ai < bi, 则排列p1 < p2.

现在, 输入1<= n <= 20, 以及询问第m小的排列.(m 保证不超过合法排列数).


solution:来自黑书p257

首先考虑求以a1, a2, .., ak 为前k个的所有合法子数列中第num小的子数列, 则只需从1到n枚举a(k+1), 直到统计的子数列个数不小于num为止. 即:  num >= sum(1 -> a(k + 1)所有合法排列数). 当然这里的合法是指如果ak > a(k + 1), 则a(k + 1) < a(k + 2). 反之亦然, 即保证数列的起伏合法性. 因而问题转化为求给定n个不同的数的排列, 并且确定第一个数为T, 以及排列的前两个数为递增或递减的所有合法排列数.

opt[n][t][0]: 表示考虑长度为n时, 第一个数为第t大的数, 前两个数递增的合法子序列个数.

opt[n][t][1]: .....................................................................................递减...........................

则有dp递推式

opt[n][t][0] = sum(k: t -> n -1) opt[n - 1][k][1];

opt[n][t][1] = sum(k: 1-> t - 1) opt[n - 1][k][0];

边界条件: opt[2][1][0] = opt[2][2][1] = 0;

求出opt数组之后, 接下来从前往后确定所要求的第m小的数列a1, a2, ..., an的每一个.

大概思路是. 每次从小到大(1-> n)枚举(没有出现过的)当前的ai, 直到累加的排列数不小于当前要求的第m小的. 我们即可确定ai就是要求的排列中的一个数. 然后更新m减去刚扫描过的不已ai为结尾的所有排列数. 作为下一次计算的参考.

注意一点: 枚举累加排列数的时候, 优先考虑当前值确定下递减的数列. 如果递减的数列数目和小于m,继续枚举当前值下的递增的数列. 

这样当计算完a1之后, 我们就能够一次确定后面的数列的增减状态(必定是交替的).

哈哈, 说起来有点啰嗦, 不过当你理解思路之后很简单就知道怎么通过dp得到的opt数组计算解序列了.


代码:

#include <iostream>#include <cstdio>#include <memory.h>using namespace std;typedef __int64 LL;const int N = 25;LL opt[N][N][2];void dp(int n){    //0: up, 1: down    memset(opt, 0, sizeof(opt));    opt[2][1][0] = opt[2][2][1] = 1;    for(int i = 3; i <= n; i ++){        for(int j = 1; j < i; j ++){            for(int k = j; k < i; k ++) opt[i][j][0] += opt[i - 1][k][1];        }        for(int j = 2; j <= i; j ++){            for(int k = 1; k < j; k ++) opt[i][j][1] += opt[i - 1][k][0];        }    }}void solve(int n, LL m){    int rs[N];    bool used[N];    LL res = m;    memset(used, false, sizeof(used));    int flag = -1, u;    for(int i = 1; i < n; i ++){        if(i != 1){            int left, right;            if(flag == 0){                left = u; right = n;            }else{                left = 1; right = u;            }            flag = 1 - flag;            int ind[N];            int cnt = 0;            for(int j = 1; j <= n; j ++){                if(! used[j]) ind[j] = ++ cnt;            }            for(int j = left; j <= right; j ++){                if(! used[j]){                    res -= opt[n - i + 1][ind[j]][flag];                    if(res <= 0){                        res += opt[n - i + 1][ind[j]][flag];                        u = j;                        break;                    }                }            }            used[u] = true; rs[i] = u;            continue;        }        for(int j = 1; j <= n; j ++)        if(! used[j]){            res -= opt[n - i + 1][j][1];            if(res <= 0){                flag = 1;                res += opt[n - i + 1][j][1];                u = j;                break;            }            res -= opt[n - i + 1][j][0];            if(res <= 0){                flag = 0;                u = j;                res += opt[n - i + 1][j][0];                break;            }        }        rs[i] = u;        used[u] = true;    }    for(int i = 1; i <= n; i ++)    if(! used[i]){        rs[n] = i;        break;    }    for(int i = 1; i <= n; i ++){        if(i != 1) printf(" ");        printf("%d", rs[i]);    }    printf("\n");}int main(){    int t;    scanf("%d", &t);    while(t --){        int n;        LL m;        scanf("%d%I64d", &n, &m);        dp(n);        solve(n, m);    }    return 0;}

有问题欢迎交流 !

0 0