动态规划(4):消除后效性

无后效性:

这是DP中最重要的一点, 他要求每个子问题的决策不能对后面其他未解决的问题产影响, 如果产生就无法保证决策的最优性, 这就是无后效性。往往需要我们找到一个合适的状态。上述的问题还有另外一个描述方式, 对于后一个节点的判断不能以前面节点的路径为依据。

例:POJ 1037 一个美妙的栅栏

N 个木棒, 长度分别为1, 2, …, N.

构成美妙的栅栏要求

1.除了两端的木棒外,每一跟木棒,要么比它左右的两根都长,要么比它左右的两根都短。
2.即木棒呈现波浪状分布,这一根比上一根长了,那下一根就比这一根短,或反过来

如下图就是N等于4时的所有可能性

这里写图片描述

问题:

符合上述条件的栅栏建法有很多种,对于满足条件的所有栅栏, 按照字典序(从左到右, 从低到高) 排序。
给定一个栅栏的排序号,请输出该栅栏, 即每一个木棒的长度.

问题抽象:

问题抽象:给定1到N 这N个数字,将这些数字高低交替进行排列 ,把所有符合情况的进行一个字典序排列,问第C个排列是一个怎样的排列

试着解题

按照上文的DP常用方法我们依次做如下分析:
·
·

直接将问题的范围缩小

设 A[i] 为i根木棒所组成的合法方案数目。看看能否找出A[i]和A[i-1]或A[i-j]之间的递推关系(所有木棒总数是i)。称i根木棒的合法方案集合为S(i)。

对于这种方法, 我们可以想到, 在i种木棍的情况下, 假设我们有N中方案数。 能否在这N种方案的最后插入第i+1根木棒, 或者如何在这I根中插入第i+1根,也跟前面的选择方案有关, 所以在这里, 这种简单粗暴的方式并不具备后效性(但不得不说 在水DP中这依旧是对绝大多数题目有效的方法)
·
·

加限制条件

在选定了某根木棒x作为第一根木棒的情况下,剩下i-1根木棒的合法方案数是A[i-1]。但是,这A[i-1]种方案,并不是每种都能和x形成新的合法方案。将第一根比第二根长的方案称为DOWN方案,第一根比第二根短的称为UP方案,则,S(i-1)中,第一根木棒比x长的DOWN方案,以及第一根木棒比x短的UP方案,才能和x构成S(i)中的方案。

这里我们对木棒的条件进行条件限制, 在加入第i根木棒的情况下我们记录当前情况的上升和下降的情况。
·
·

继续推导

置A[i] = 0。先枚举x。然后针对每个x,枚举x后面的那根木棒y。如果 y > x(x < y的情况类推),则:

A[i] += 以y打头的DOWN方案数

但以y打头的DOWN方案数,又和y的长短有关。

于是难以直接从 A[i-1]或 A[i-j]推出 A[i]

也就是这里依旧不具备后效性
·
·
·

继续加限制条件

考虑将A[i]这种粗略的状态描述方式细化,即加上限制条件后分类。设

A[i] = Σ B[i][k] k = 1….i

B[i][k] 是S(i)中以第k短的木棒打头的方案数。

尝试对 B 进行动归。第k短,指的是i根木棒中第k短。
·
·
·

综合上述的全部限制条件

B[i][k] = Σ B[i-1][M](D OWN) + Σ B[i-1][N](U P)

M = k … i-1 , N = 1… k-1

还是没法直接推。于是把B再分类细化:

B[i][k] = C[i][k][D OWN] + C[i][k][U P]

C[i][k][DOWN] 是S(i)中以第k短的木棒打头的DOWN方案数。然后试图对C进行动归

C[i][k][UP] = Σ C[i-1][M][DOWN]
M = k … i -1
C[i][k][DOWN] = Σ C[i-1][N][UP]
N = 1… k-1
初始条件:C[1][1][UP]=C[1][1][DOWN] = 1

思路总结

当选取的状态,难以进行递推时(分解出的子问题和原问题形式不一样,或不具有无后效性),考虑将状态增加限制条件后分类细化即增加维度,然后在新的状态上尝试递推

另外这题目的另一个难点就是关于排列计数的问题, 这个问题在这里暂时不讲

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 25

using namespace std;

long long dp[maxn][maxn][2];

void init(int n)
{
    memset(dp, 0, sizeof(dp));
    dp[1][1][0] = 1;//up
    dp[1][1][1] = 1;//down
    for (int j = 2; j <= n; j++)
    {
        for (int i = 1; i <= j; i++)
        {
            for (int x = i; x < j; x++)
                dp[i][j][0] += dp[x][j - 1][1];
            for (int x = 1; x < i; x++)
                dp[i][j][1] += dp[x][j - 1][0];
        }
    }
}

int ans[maxn];

void solve(int n, long long c)
{//下面主要是排列组合的相关问题
    int tn = n;
    int flag, cur;
    long long sum = 0LL;
    for (int i = 1; i <= n; i++)
    {
        if (sum + dp[i][n][0] + dp[i][n][1] >= c)
        {
            ans[1] = i;
            c -= sum;
            cur = i;
            break;
        }
        sum += (dp[i][n][0] + dp[i][n][1]);
    }
    if (c < dp[cur][n][1])
        flag = 1;
    else
    {
        c -= dp[cur][n][1];
        flag = 0;
    }
    --n;
    int len = 2;
    while (n > 0)
    {
        if (flag == 0)
        {
            for (int i = cur; i <= n; i++)
            {
                if (dp[i][n][1] >= c)
                {
                    cur = i;
                    ans[len++] = cur;
                    break;
                }
                c -= dp[i][n][1];
            }
        }
        else
        {
            for (int i = 1; i<cur; i++)
            {
                if (dp[i][n][0] >= c)
                {
                    cur = i;
                    ans[len++] = cur;
                    break;
                }
                c -= dp[i][n][0];
            }
        }
        --n;
        flag = 1 - flag;
    }
    int vis[maxn] = { 0 };
    for (int i = 1; i <= tn; i++)
    {
        for (int j = 1; j <= tn; j++)
        {
            if (vis[j] == 0)
            {
                ans[i]--;
                if (ans[i] == 0)
                {
                    printf("%d%c", j, i == tn ? '\n' : ' ');
                    vis[j] = 1;
                    break;
                }
            }
        }
    }
}

int main(void)
{
    int T, n;
    long long c;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d %lld", &n, &c);
        solve(n, c);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章