0x50 动态规划(0x5C 计数类DP)例题3:装饰围栏(题解)(计数类DP讲解,确定第k个排列)

Posted 敌敌畏58

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0x50 动态规划(0x5C 计数类DP)例题3:装饰围栏(题解)(计数类DP讲解,确定第k个排列)相关的知识,希望对你有一定的参考价值。

计数类DP一般就是确定DP状态,DP出排名范围,然后不断逼近。

题意

题目链接

【题目描述】
 有 N 块长方形的木板,长度分别为1,2,…,N,宽度都是1。
 现在要用这 N 块木板组成一个宽度为 N 的围栏,满足在围栏中,每块木板两侧的木板要么都比它高,要么都比它低。
 也就是说,围栏中的木板是高低交错的。
 我们称“两侧比它低的木板”处于高位,“两侧比它高的木板”处于低位。
 显然,有很多种构建围栏的方案。
 每个方案可以写作一个长度为N的序列,序列中的各元素是木板的长度。
 把这些序列按照字典序排序,如下图所示,就是 N=4 时,所有满足条件的围栏按照木板长度的字典序排序后的结果。 

 现在给定整数C,求排名为C的围栏中,各木板的长度从左到右依次是多少。 
注:两侧的木板指的是相邻的两块木板 
【输入格式】
 第一行包含整数K,表示一共有K组数据。
 接下来K行,每行包含一组数据,包括两个整数N和C。 
 【输出格式】
 每组数据输出一行结果,结果表示排名为C的围栏中,各木板的长度从左到右排成的序列。
 同行数据用空格隔开。 
 【数据范围】
1<=N<=20
 0<C<2^63 
【输入样例】
2
 2 1
 3 3 
【输出样例】
1 2
 2 3 1 

题解

\(f[i][j][0/1]\),分别表示的是点集在\(1\)~\((n-i+1)\),第一个位置选的是\(j\)且第一个位置是\(down(0)/up(1)\)的时候的方案数。

那么很明显的一个状态转移方程是:
\(f[i-1][j][0]=\sum\limits_{k=j}^{n-i+1}f[i][k][1]\)

意思就是把\(s(i)\)点集中的\(j\)~\(n-i+1\)\(1\),把\(j\)插到原本\(j\)的位置上,就成了\(s(i-1)\)的点集了。

那么很显然:
\(f[i-1][j][1]=\sum\limits_{k=1}^{i-1}f[i][k][0]\)

然后我们可以对于第\(1\)位的数字,通过看排名范围来确定第一位是什么数字,\(up\)还是\(down\),然后后面也一样慢慢逼近就行了。

#include<cstdio>
#include<cstring>
#define  N  30
using  namespace  std;
typedef  long  long  LL;
LL  f[N][N][2],m;//0为up,1为down
int  n; 
int  a[N];
inline  int  findkth(int  k)
{
    int  x=0;
    for(int  i=1;i<=n;i++)
    {
        x+=a[i];
        if(x==k)
        {
            a[i]=0;
            return  i;
        }
    }
}
int  b[N];
int  main()
{
    int  T;scanf("%d",&T);
    while(T--)
    {
        memset(f,0,sizeof(f));
        scanf("%d%lld",&n,&m);
        for(int  i=1;i<=n;i++)a[i]=1;
        f[n][1][0]=f[n][1][1]=1;
        for(int  i=n-1;i>=1;i--)
        {
            for(int  j=n-i+1;j>=1;j--)
            {
                //现在处理的是up的情况
                for(int  k=1;k<j;k++)f[i][j][1]+=f[i+1][k][0];
                for(int  k=n-i+1;k>=j;k--)f[i][j][0]+=f[i+1][k][1];
            }
        }
        //计数DP
        int  type,id;
        for(int  i=1;i<=n;i++)//确定第一位是多少,up还是down 
        {
            if(f[1][i][1]<m)m-=f[1][i][1];
            else{b[1]=findkth(i);id=i;type=1;break;}
            if(f[1][i][0]<m)m-=f[1][i][0];
            else{b[1]=findkth(i);id=i;type=0;break;}
        }
        for(int  i=2;i<=n;i++)
        {
            int  st=1,ed=n-i+1;type^=1;
            if(type==0)ed=id-1;
            else  st=id;
            for(int  j=st;j<=ed;j++)
            {
                if(f[i][j][type]<m)m-=f[i][j][type];
                else{b[i]=findkth(j);id=j;break;}
            }
        }
        for(int  i=1;i<n;i++)printf("%d ",b[i]);
        printf("%d\n",b[n]);
    }
    return  0;
}

以上是关于0x50 动态规划(0x5C 计数类DP)例题3:装饰围栏(题解)(计数类DP讲解,确定第k个排列)的主要内容,如果未能解决你的问题,请参考以下文章

动态规划

《算法竞赛进阶指南》0x5C计数DP Gerald & Giant Chess

动态规划_计数类dp_数位统计dp_状态压缩dp_树形dp_记忆化搜索

动态规划数学期望/概率DP/期望DP详解

算法动态规划DP自学笔记 入门:基本知识+经典例题

DP-01动态规划算法原理介绍