「动态规划」例题之数学相关的状态和转移方程的设计

0x50「动态规划」例题

这类问题可能常用到的相关知识:容斥原理,组合数递推,乘法逆元(用于计算组合数),快速幂。

计数类DP

为了保证DP计数过程中的不重不漏,这一类DP问题往往会有些特殊的“子结构”或者说是“阶段”,我们的原则是——找到一个“基准点”,围绕它构造一个“不可分割”的整体,避免子问题重叠。

例题

POJ1737 Connected Graph
状态表示:F[i]表示i个节点的无向连通图有多少个
设计转移方程时,我们考虑将1号点作为基准点。首先用容斥原理考虑,n个点的无向图连通图个数=n个点的无向图个数-n个点的无向不连通图。其中,任意两个点之间可以建边,总共最多可建条边,每条边可以有两种选择:连接或断开。因此n个点的无向图个数是。考虑计算n个点的无向不连通图的方案数,由于一个无向不连通图必然由多个连通块构成,于是设1号点所在的连通块的大小为j,那么从2...n这n-1个点中选出j-1个点与1号点构成连通块的方案数是,剩下的点随意组合,方法数为。
转移方程:,其中 且
边界:
目标:
由于n较小,可直接用帕斯卡公式递推组合数,双重循环DP,时间复杂度:
PS:本题因为答案很大,所以需要高精度,用这个公式实现高精度,需要维护高精度加法,高精度减法,高精度乘法,高精度低精度乘法,高精度快速幂五种操作。于是,代码中method_1是不含高精度的,method_2是含有高精度,但是使用的直接递推(非容斥原理),代码如下

/*

*/
#define method_2
#ifdef method_1
/*
n>9时int溢出 
https://blog.csdn.net/icefox_zhx/article/details/79055962

*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=50+5;
const int INF=0x3f3f3f3f;
int n,c[maxn][maxn],f[maxn];
void pre1(){
    c[0][0]=1;
    for(int i=1;i<=50;i++){
        for(int j=0;j<=i;j++){
            c[i][j]=c[i-1][j-1]+c[i-1][j];
        }
    }
}
void pre2(){
    f[1]=1,f[2]=1,f[3]=4;
    for(int i=3;i<=50;i++){
        f[i]=(1<>n&&n){
        cout<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=50+5;
const int maxl=400+5;
const int INF=0x3f3f3f3f;
ll n,c[maxn][maxn],bin[maxn];
struct bigint{
    ll a[maxl],n;
    bigint(){memset(a,0,sizeof(a)),n=0;}
    ll& operator[](int x){return a[x];}
    void print(){for(int i=n;i>=1;i--) cout<>n&&n){
        f[n].print();E;
    }
    return 0;
}
#endif
#ifdef method_3
/*

*/

#endif

POJ1037 A Decorative Fence
由于要求排名C的栅栏的形状,所以用类似数位DP中的常用手段——试填法。具体地说,我们从小到大枚举第一块木板的高度,若后面n-1块木板的排名小C则继续循环,同时令C减去第一块高度为len的方案数。若后面n-1块木板的排名大于等于C则第一块木板的高度就应该是C,这是试填成功,break出当前循环,继续试填下一块木板。为了高效计算,我们预处理出当第一块木板高度确定时,剩下所有木板构成的方案数。
状态表示:F[i,j,k]表示i块长度不同的木板,最左边木板长度从小到大排在第j位,位置状况为k(k=0表示处于低位,k=1表示处于高位)的方案数。
设计转移方程时,由于高低位相间,所以阶段i和阶段i-1之间构成重叠子问题,使得DP原理合法。
转移方程:


PS:这个转移方程里有一点格外重要,即p的范围问题。首先,若最左边的木板高度上排在第j位,同时处于低位。那么它的前一块木板必然处于高位,并且其高度排名大于等于j,注意这里是大于等于的原因在于去掉最左边的木板时,所有高度排名大于它的木板的高度排名都会-1,而所有高度小于它的木板的高度排名不变,所以才出现了p可以取到j的情况。而若最左边的木板若是处于高位,那么去掉它之后,下一个处于低位的木板的排名必然严格小于j。
PPS:第一块木板可能既处于高位,也可能处于低位,所以单独处理。
边界:
目标:准备拼凑答案状态,方法已在上面讲过,这里不再赘述
三重循环DP,时间复杂度:,其中试填法复杂度:
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=20+5;
const int INF=0x3f3f3f3f;
int T,n,used[maxn];
ll m,f[maxn][maxn][2];
void pre(){
    f[1][1][0]=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() {
    ios::sync_with_stdio(false);
//  freopen("A Decorative Fence.in","r",stdin);
    pre();
    cin>>T;
    while(T--){
        cin>>n>>m;
        memset(used,0,sizeof(used));
        int last,k;
        for(int j=1;j<=n;j++){
            if(f[n][j][1]>=m){
                last=j,k=1;
                break;
            }
            else m-=f[n][j][1];
            if(f[n][j][0]>=m){
                last=j,k=0;
                break;
            }
            else m-=f[n][j][0];
        }
        cout<last)){
                    if(f[n-i+1][j][k]>=m){
                        last=len;
                        break;
                    }
                    else m-=f[n-i+1][j][k];
                }
            }
            cout<

数位统计DP

数位DP往往是通过预处理DP,然后通过类似倍增DP中的拼凑思想——“试填法”求解。(例题:POJ 3208、BZOJ 1799)

你可能感兴趣的:(「动态规划」例题之数学相关的状态和转移方程的设计)