动态规划入门

Posted pprp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划入门相关的知识,希望对你有一定的参考价值。

2017-09-01 11:29:43

writer:pprp

看sprout台湾大学acm教学视频的第一部分:

里边涉及到四道小例题

感觉很好就拿来写了写:

题意还有代码说明都在代码中:

1、最基础的骨牌问题:

/*
@param:dp 入门
@writer:pprp
@declare:最经典最简单的dp
@begin:9:00
@end:10:00
@date:2017/9/1
*/

#include <bits/stdc++.h>

using namespace std;

//未优化的最基础的
int fun(int n)
{
    if(n == 1) return 1;
    if(n == 2) return 2;

    return fun(n-1) + fun(n-2);
}

//自顶向下的记忆化
//top down 小心递回过深
int dp[101] = {1,1,2};
int fun2(int n)
{
    if(dp[n] != 0)
        return dp[n];
    return fun2(n-1) + fun(n-2);
}

//自底向上
//子问题一定要比母问题要先跑到,注意递回跑法
int dp2[101] = {0};
void build()
{
    dp2[1] = 1;
    dp2[2] = 2;
    for(int i = 3 ; i < 101; i++)
     dp2[i] = dp2[i-1] + dp2[i-2];
}
int fun3(int n)
{
    return dp2[n];
}

int main()
{
    build();
    int a;
    while(cin >> a)
    {
        cout << fun(a) << endl;
        cout << fun2(a) << endl;
        cout << fun3(a) << endl;
    }

    return 0;
}

2、涂色问题:题意见代码头

/*
@param:dp 入门
@writer:pprp
@declare:最经典最简单的dp,给红绿蓝三种颜色,让你求出蓝绿不相邻的排列n个元素的情况
@begin:10:10
@end:10:28
@date:2017/9/1
*/

#include <bits/stdc++.h>

using namespace std;

/*
状态确定:按照最后一位的颜色确定状态
f(n,0):最后一位是红色的数量
f(n,1):最后一位是绿色的数量
f(n,2):最后一位是蓝色的数量
状态转移:
f(n,0) = f(n-1,0) + f(n-1,1) + f(n-1,2)
f(n,1) = f(n-1,0) + f(n-1,1)
f(n,2) = f(n-1,0) + f(n-1,2)
初始条件:
f(1,0) = 1;
f(1,1) = 1;
f(1,2) = 1;
最终答案:
f(n,0) + f(n,1) +f(n,2);
*/

// top down
int dp1[101][3] = {0};

void init()
{
    dp1[1][0] = dp1[1][1] = dp1[1][2] = 1;
}

int fun(int n,int m)
{
    if(dp1[n][m] != 0)
        return dp1[n][m];

    if(m == 0) dp1[n][0] = fun(n-1,0) + fun(n-1,1) + fun(n-1,2);
    if(m == 1) dp1[n][1] = fun(n-1,0) + fun(n-1,1);
    if(m == 2) dp1[n][2] = fun(n-1,0) + fun(n-1,2);

    return dp1[n][m];
}

//bottom up
int dp2[101][3] = {3};
int build()
{
    dp2[1][0] = dp2[1][1] = dp2[1][2] = 1;
    for(int i = 2; i < 101 ; i++)
    {
        dp2[i][0] = dp2[i-1][0] + dp2[i-1][1] + dp2[i-1][2];
        dp2[i][1] = dp2[i-1][0] + dp2[i-1][1];
        dp2[i][2] = dp2[i-1][0] + dp2[i-1][2];
    }
}
int fun2(int n)
{
    return dp2[n][0] + dp2[n][1] + dp2[n][2];
}

//run
int main()
{
    int a;
    cin >> a;
    init();
    build();

    cout << fun(a,0) + fun(a,1) + fun(a,2) << endl;
    cout << fun2(a) << endl;
    return 0;
}

3、骨牌问题2,加了一个L型骨牌

/*
@param:dp 入门
@writer:pprp
@declare:最经典最简单的dp, 给你2*1 & 3*1 L型骨牌填满2*n的格子,有几种拍法
@begin:10:36
@end:11:00
@date:2017/9/1
*/

#include <bits/stdc++.h>

using namespace std;

/*
状态分析:
f(n) 代表的是放n*2个格子时候的方法数
状态转移:
如果最后那块放2*1的:f(n) = f(n-1) + f(n-2)
如果最后那块放3*1的:f(n) = f(n-3) + f(n-4) + ... + f(0),由于对称的关系,要乘2
综合起来: f(n) = f(n-1) + f(n-2) + 2 * (f(n-3) + f(n-4) +...+ f(0))
边界状态:f(0) = 1; f(1) = 1; f(2) = 2;
*/

//top down
int dp[101] = {0};
void init()
{
    dp[0] = dp[1] = 1;
    dp[2] = 2;
}
int fun(int n)
{
    init();
    if(dp[n] != 0) return dp[n];
    dp[n] = 2 * fun(n-1) + fun(n-3);
    return dp[n];
}

//bottom up
int dp2[101] = {};

void build()
{
    dp2[0] = dp2[1] = 1;
    dp2[2] = 2;

    for(int i = 3 ; i < 101 ; i++)
        dp2[i] = 2 * dp2[i-1] + dp2[i-3];
}

int fun2(int n)
{
    return dp2[n];
}

int main()
{
    int a;
    srand((int)time(NULL));
    a = rand()%100;

    build();
    cout << fun(a) << endl;
    cout << fun2(a) << endl;

    return 0;
}

4.找到不相邻的数的最大和

/*
@param:dp 入门
@writer:pprp
@declare:最经典最简单的dp,给你一个正整数阵列,
从里边取出不相邻的数,问你取出数字和最大为多少?
@begin:1:05
@end:11:25
@error:应该是从1开始不是从0开始
@date:2017/9/1
*/

#include <bits/stdc++.h>

using namespace std;

/*
状态分析:
f(n)代表的是当前取了这个位置以后的最大数字和
状态转移:
f(n) = max(f(n-2),f(n-3) + arr[n];
边界状态:f(0) = 0, f(1) = arr[1], f[2] = max(arr[1],arr[2])
*/

//top down
int arr[101] = {};
int dp[110] = {};

void init()
{
    dp[0] = 0;
    dp[1] = arr[1];
    dp[2] = max(arr[1],arr[2]);
    dp[3] = max(arr[1]+arr[3],arr[2]);
}

int fun(int n)
{
    init();
    if(dp[n] != 0)
        return dp[n];
    dp[n] = max(fun(n-2),fun(n-3)) + arr[n];
    return dp[n];
}

int dp2[110] = {};
//bottom up
void build()
{
    dp2[0] = 0;
    dp2[1] = arr[1];
    dp2[2] = max(arr[1],arr[2]);

    for(int i = 3; i < 101 ; i++)
    {
        dp2[i] = max(dp2[i-2],dp2[i-3]) + arr[i];
    }
}

int fun2(int n)
{
    return dp2[n];
}
int main()
{
    freopen("in.txt","r",stdin);
    int n;
    cin >> n;
    for(int i = 1 ; i <= n ; i++)
        cin >> arr[i];

    init();
    build();

    cout << max(fun(4),fun(5)) << endl;
    cout << max(fun2(4),fun2(5)) << endl;
    return 0;
}

总结:

状态确定很重要,利用对称的关系分析可以简化,

动态规划是一个走一步算一步的算法过程,不要全局的去分析,否则越分析越乱,按照每一步的走向来确定状态转移方程

做的多了动态规划才能有灵感

 

以上是关于动态规划入门的主要内容,如果未能解决你的问题,请参考以下文章

leetcode专项 动态规划入门

leetcode专项 动态规划入门

leetcode专项 动态规划入门

动态规划入门:01背包问题

递归?这次真不行 —— “动态规划” 入门理解(下)

详细实例说明+典型案例实现 对动态规划法进行全面分析 | C++