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

0x50「动态规划」例题

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

计数类DP

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

例题

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

/*

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

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>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<<c[i][2]);
        for(int j=1;j<=i-1;j++){
            f[i]-=f[j]*(1<<c[i-j][2])*c[i-1][j-1];
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    freopen("Connected Graph.in","r",stdin);
    pre1();
    pre2();
    while(cin>>n&&n){
        cout<<f[n]<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>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<<a[i];}
    friend bigint operator+(bigint x,bigint y){
        bigint res;res.n=max(x.n,y.n);
        for(int i=1;i<=res.n;i++) res[i]=x[i]+y[i];
        for(int i=1;i<=res.n;i++) res[i+1]+=res[i]/10,res[i]%=10;
        if(res[res.n+1]) res.n++;
        return res;
    }
    friend bigint operator*(bigint x,bigint y){
        bigint res;res.n=x.n+y.n-1;
        for(int i=1;i<=x.n;i++) for(int j=1;j<=y.n;j++)res[i+j-1]+=x[i]*y[j];
        for(int i=1;i<=res.n;i++) res[i+1]+=res[i]/10,res[i]%=10;
        while(res[res.n+1]) res.n++,res[res.n+1]+=res[res.n]/10,res[res.n]%=10;
        return res;
    }
    friend bigint operator*(bigint x,ll y){
        bigint res;res.n=x.n;
        for(int i=1;i<=x.n;i++) res[i]+=x[i]*y;
        for(int i=1;i<=res.n;i++) res[i+1]+=res[i]/10,res[i]%=10;
        while(res[res.n+1]) res.n++,res[res.n+1]+=res[res.n]/10,res[res.n]%=10;
        return res;
    }
}f[maxn];
void pre1(){
    c[0][0]=1;
    bin[0]=1;
    for(int i=1;i<=50;i++){
        bin[i]=bin[i-1]<<1;
        for(int j=0;j<=i;j++){
            c[i][j]=c[i-1][j-1]+c[i-1][j];
        }
    }
}
void pre2(){
    f[1][1]=1,f[1].n=1,f[2][1]=1,f[2].n=1;
    for(int i=3;i<=50;i++){
        for(int j=1;j<=i-1;j++){
            f[i]=f[i]+f[i-j]*f[j]*c[i-2][j-1]*(bin[j]-1);
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Connected Graph.in","r",stdin);
    pre1();
    pre2();
    while(cin>>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原理合法。
转移方程:
F[i,j,0]=\sum_{p=j}^{i-1}F[i-1,p,1]
F[i,j,1]=\sum_{p=0}^{j-1}F[i-1,p,0]
PS:这个转移方程里有一点格外重要,即p的范围问题。首先,若最左边的木板高度上排在第j位,同时处于低位。那么它的前一块木板必然处于高位,并且其高度排名大于等于j,注意这里是大于等于的原因在于去掉最左边的木板时,所有高度排名大于它的木板的高度排名都会-1,而所有高度小于它的木板的高度排名不变,所以才出现了p可以取到j的情况。而若最左边的木板若是处于高位,那么去掉它之后,下一个处于低位的木板的排名必然严格小于j。
PPS:第一块木板可能既处于高位,也可能处于低位,所以单独处理。
边界:F[1,1,0]=F[1,1,1]=1
目标:准备拼凑答案状态,方法已在上面讲过,这里不再赘述
三重循环DP,时间复杂度:O(n^{3}),其中试填法复杂度:O(n^{2})
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>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<<" ";
        used[last]=1;
        for(int i=2;i<=n;i++){
            k^=1;
            int j=0;
            for(int len=1;len<=n;len++){
                if(used[len]) continue;
                j++;
                if((k==0&&len<last)||(k==1&&len>last)){
                    if(f[n-i+1][j][k]>=m){
                        last=len;
                        break;
                    }
                    else m-=f[n-i+1][j][k];
                }
            }
            cout<<last<<" ";
            used[last]=1;
        }
        cout<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

数位统计DP

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容