动态规划DynamicProgramming入门与实战(C++)


方便自己预习也帮大佬们复习

概述

思想:
是多种算法思想的组合算法
1.穷举思想(一直选择)
2.贪心思想(最优选择)
3.空间换时间
4.分治思想(拆分问题为子问题直至子问题直接解决)
5.状态(状态表示,状态转移)
6.*递推、递归

过程:
判断题目是否为DP
将阶段划分
状态表示(设计程序)
状态转移----最后一步、子问题
转移时的限制条件

题目类型:
1.计数(从入口到出口有多少种走法,选k个数的和为Sum有多少种选择…)
2.求最值(从入口到出口的权和最大/最小是什么,最长上升序列…)
3.求存在性(经典输赢博弈,能否选k个数和为Sum…)


目前题型不多,会慢慢更新
下列代码均为AC代码,请放心食用

经典入门题

数塔

HDUOJ测试链接

题目描述:
有如下数塔,要求从最顶层走到最底层,每一步只能走到相邻的结点,经过结点的数字之和最大是多少?
在这里插入图片描述
输入描述:
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。

输出描述:
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。

样例:
输入:
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出:
30
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.确定状态:
  最后一步:在更底层的数塔(从i-1行到第n行)中选择哪个数塔并再加一层使路径更大
  子问题:更底层的数塔的最大路径是多少?
2.转移方程:dp[i][j]表示以num[i][j]为塔尖的数塔的最大路径
  dp[i][j] += max(dp[i + 1][j], dp[i + 1][j + 1]);
3.计算顺序:dp[n-1][1.2.3.........n-2.n-1].dp[n-2][1.2.3.........n-3.n-2]........
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
using namespace std;
int dp[105][105];
int main()
{
    
    
    int cass;
    for (cin>>cass; cass;cass--)
    {
    
    
        mm(dp, 0);
        int n;
        cin >> n;
        rep1(i, 1, n)
            rep1(j, 1, i) 
                cin >> dp[i][j];
        //层层向上筛选
        rep2(i,n-1,1)
            rep1(j, 1, i) 
                dp[i][j] += max(dp[i + 1][j], dp[i + 1][j + 1]);
        cout << dp[1][1] << endl;
    }
}

不同的路径

LintCode测试链接

题目描述
有一个机器人的位于一个 m × n 个网格左上角。
机器人每一时刻只能向下或者向右移动一步。机器人试图达到网格的右下角。
问有多少条不同的路径?
(m<=100,n<=100)
样例:
输入:1 3
输出:1

输入:3 3
输出:6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
//计数型
1.确定状态:
  最后一步:无论机器人用何种方式走到右下角,总有最后挪动的一步
  子问题:前一步机器人一定在(m-2,n-1)(m-1,n-2)位置
2.转移方程 dp[i][j]为机器人有多少种方式走到特定位置//变量有两个,所以开二维数组
  dp[i][j]=dp[i-1][j]+dp[i][j-1]//加法原理:无重复,无遗漏
3.计算顺序
  从左到右推一行
  再从上往下推
class Solution {
    
    
public:
    int uniquePaths(int m, int n) {
    
    
        
        int dp[n][m];//dp[i][j]代表有多少种方式可以从左上角走到(j,i)的位置
        
        for(int i=0;i<n;i++)
        {
    
    
            for(int j=0;j<m;j++)
            {
    
    
                if(i==0||j==0) dp[i][j]=1;//初始化
                else           dp[i][j]=dp[i-1][j]+dp[i][j-1];//加法原理
            }
        }

        return dp[n-1][m-1];
    }
};

Jump Game

LintCode测试链接

题目描述:
给出一个非负整数数组,你最初定位在数组的第一个位置。   
数组中的每个元素代表你在那个位置可以跳跃的最大长度。    
判断你是否能到达数组的最后一个位置。

样例:
输入 : [2,3,1,1,4]
输出 : true

输入 : [3,2,1,0,4]
输出 : false

注意事项:
数组A的长度不超过5000,每个元素的大小不超过5000
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.确定状态:
  最后一步:如果青蛙可以跳到len-2,考虑最后一步
  最后一步不超过最大距离:n-1-i<=a[i]
  子问题:可不可以跳到a[len-2]
2.转移方程:判断能否从i跳到j
  if(f[i]&&i+a[i]>=j) dp[j]=true;
3.顺序
class Solution {
    
    
public:
    bool canJump(vector<int> &A) {
    
    
        int len=A.size();
        bool dp[len+10];//判有无可能跳到dp[i]
        dp[0]=true;//本来就在首位
        
        for(int i=1;i<len;i++)
        {
    
    
            dp[i]=false;
            for(int j=0;j<i;j++)//枚举之前的来判断此时的
            {
    
    
                if(dp[j]&&j+A[j]>=i) //A[j]是最大长度,所以要保证最大的和当时位置可以跳过目标位置即可
                {
    
    
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[len-1];
    }
};

入门总结

DP做题思维模块组成部分
1.确定状态(研究最后一步+划分子问题)
2.转移方程(将子问题递推用公式表现出)
3.初始条件和边界情况(细心全面的考虑)
4.计算顺序(利用前面的计算结果进行递推)


简单dp题

被3整除的子序列个数

牛客测试链接

题目描述
给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除
答案对1e9+7取模

输入描述:
输入一个字符串,由数字构成,长度小于等于50

输出描述:
输出一个整数

样例:
输入
132
输出
3

输入
9
输出
1

输入
333
输出
7

输入
123456
输出
23

输入
00
输出
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.确定状态:
  最后一步:合算前面的余数为012的个数与不带最后一个还是012的个数
  子问题:求前i-1个数余数为012的个数
2.转移方程:
  dp[i][j]为前i个数余数为j的子序列个数
  dp[i][j]+=dp[i-1][j]+dp[i-1][j+3-(str[i]-'0')%3]
3.计算顺序:
  dp[0][0] dp[0][1] dp[0][2] dp[1][0] dp[1][1] dp[1][2]...............
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
using namespace std;
const int mod=1e9+7;
int main()
{
    
    
    string str;
    cin>>str;
    int len=str.size();
    int dp[1000][3];//dp[i][j]表示前i个数中对三取模为j的个数
    memset(dp,0,sizeof(dp));
    dp[0][(str[0]-'0')%3]=1;//初始已知dp
    
    for(int i=1;i<len;i++)
    {
    
    
        int temp=(str[i]-'0')%3;
        dp[i][temp]=(dp[i][temp]+1)%mod;//此时多了其本身的一个可能
        for(int j=0;j<3;j++)
        {
    
    
            dp[i][j]+=(dp[i-1][j]+dp[i-1][(j+3-temp)%3])%mod;//子问题:划分为前i-1个余数j的与前i-1个在减去该temp后余数还是j的
        }
    }
    cout<<dp[len-1][0]%mod;
    return 0;
}

三步问题----------水

DP并不所有都那么绕,来个水题提提信心

LeetCode测试链接

题目描述:
三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

样例:
输入:n = 3
输出:4

输入:n = 5
输出:13

n范围在[1, 1000000]之间
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
思想构建中坚持上面的流程即可
这题较水,不说思路了
class Solution {
    
    
public:
    const int mod=1e9+7;
    long long waysToStep(int n) {
    
    
        long long dp[n+10];
        dp[0]=0,dp[1]=1,dp[2]=2,dp[3]=4;
        for(int i=4;i<=n;i++) dp[i]=(dp[i-1]+dp[i-2]+dp[i-3])%mod;
        return dp[n];
    }
};

逻辑表达式

想用这道题说明dp和dfs的关系

AtCoder测试链接

题目描述:
现在有 N 个字符串 S1,…,SN,每个字符串的内容要么是 AND,要么是OR。
希望你找到,有多少个不同的数组 X 满足如下条件:
其中 X 数组的长度为 n + 1.(X0 … Xn)
X 数组中仅有 True 和 False 组成。(1 和 0)
通过 X 数组和字符串的“与逻辑”,“或逻辑”运算,得到长度为 n 的数组 Y,要求 Yn = True. (Yn = 1)
特殊的 Y0 = X0.
for i >= 1:
​ if(S[i] = “AND”) Yi = Yi-1 ∧ Xi;
​ else if(S[i] = “OR”) Yi = Yi-1 ∨ Xi;

1≤N≤60
Si is AND or OR.

输入描述:
数据输入格式如下:
N
S1
.
.
.
SN

输出描述:
请输出满足条件的 X 数组的数量。

样例:
输入:
2
AND
OR
输出:
5
说明:
例如, 如果 (x0,x1,x2) = (True,False,True), 我们可以得到 y2=True, 计算过程如下:
y0 = x0 = True
y1 = y0 ∧ x1 = True ∧ False = False
y2 = y1 ∨ x2 = False ∨ True = True
所有满足条件的 5 种 X 数组如下:
(True,True,True)
(True,True,False)
(True,False,True)
(False,True,True)
(False,False,True)

输入:
5
OR
OR
OR
OR
OR
输出:
63
说明:
除了全部都是 False 的 X 数组不满足,其他 63 种都满足条件。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

dfs思路:(TLE)
拿到题认为是个穷举题,只需要记录每次运算结果即可,立即想到dfs递归解法,可惜TLE
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
using namespace std;
typedef long long ll;
ll cnt;
int n;
string s[100];
void dfs(int i, int sumFlag)//sumFlag记录枚举时的结果
{
    
    
    if (i>=n)//边界
    {
    
    
        cnt+=sumFlag;//sumFlag为0就加0,为1则顺应题目可加1
        return;
    }
    //1和0的情况都枚举一遍
    if (s[i] == "AND")
    {
    
    
        dfs(i + 1, 0&sumFlag);
        dfs(i + 1, 1&sumFlag);
    }
    else if (s[i] == "OR")
    {
    
    
        dfs(i + 1, 0|sumFlag);
        dfs(i + 1, 1|sumFlag);
    }
}
int main()
{
    
    
    scanf("%d", &n);
    getchar();
    for (int i = 0; i < n; i++)
        cin >> s[i];
    cnt = 0;
    dfs(0, 0);//从0起手
    dfs(0, 1);//从1起手
    cout << cnt;
}
//不难判断时间复杂度为O(2^n)

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

dp思路:
//如果说上面的dfs为逆向无记忆搜索,我认为dp就是一种记忆化正向递推(递推不比递归简单吗?狗头)
1.确定状态
  最后一步:判断倒数第二步插入truefalse得到的结果是什么
  子问题:分割为倒数第二步结果对应的操作是什么
2.转移方程:
  dpTrue[i]代表前i步得到true的结果有多少种方案
  dpFalse[i]代表前i步得到false的结果有多少种方案
  s[i]=="AND"? dpTrue[i] = dpTrue[i - 1], dpFalse[i] = dpFalse[i - 1] * 2 + dpTrue[i - 1]:dpTrue[i] = dpTrue[i - 1] * 2 + dpFalse[i - 1], dpFalse[i] = dpFalse[i - 1];
  //若是AND,
  //dpTrue[i]对应的结果是前i-1步得到true结果的方案数(只有true and true才得true)
  //dpFalse[i]对应的结果是前i-1步得到的false结果的方案数乘2(true或false and false都为false)加得true的方案数(false and true为false)
  //若是OR,
  //dpFalse[i]对应的结果是前i-1步得到false结果的方案数(只有false or false才得false)
  //dpTrue[i]对应的结果是前i-1步得到的true结果的方案数乘2(true或false or true都为true)加得false的方案数(true and false为true)
3.初始化:dpTrue[0]=dpFalse[0]=1,因为开始随便拿truefalse打头都是1
4.顺序:从1到n
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
using namespace std;
typedef long long ll;
int n;
string s[100];
int main()
{
    
    
    scanf("%d", &n);
    getchar();
    for (int i = 1; i <= n; i++)
        cin >> s[i];
    ll dpTrue[100], dpFalse[100];
    memset(dpTrue, 0, sizeof(dpTrue));
    memset(dpFalse, 0, sizeof(dpFalse));
    dpTrue[0] = dpFalse[0] = 1;
    for (int i = 1; i <= n;i++)
    {
    
    
        if(s[i]=="AND")
            dpTrue[i] = dpTrue[i - 1], dpFalse[i] = dpFalse[i - 1] * 2 + dpTrue[i - 1];
        else
            dpTrue[i] = dpTrue[i - 1] * 2 + dpFalse[i - 1], dpFalse[i] = dpFalse[i - 1];
    }
    cout << dpTrue[n];
}
//时间复杂度为O(n) Nice!

丑数

HDUOJ测试链接

题目描述:
因数只含2.3.5.7的正整数被称为丑数,下面给出前二十个丑数:1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 24, 25, 27, … 请你求出第n个丑数

输入描述:
多实例输入整数n代表要找第n个丑数,以0结束

输出描述:
The n(th.st.nd.rd) humble number is f[n]. 前面那个n后的是序号后缀表示

样例:
输入:
1
2
3
4
11
12
13
21
22
23
100
1000
5842
0
输出:
The 1st humble number is 1.
The 2nd humble number is 2.
The 3rd humble number is 3.
The 4th humble number is 4.
The 11th humble number is 12.
The 12th humble number is 14.
The 13th humble number is 15.
The 21st humble number is 28.
The 22nd humble number is 30.
The 23rd humble number is 32.
The 100th humble number is 450.
The 1000th humble number is 385875.
The 5842nd humble number is 2000000000.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.确定状态:
  最后一步:如何在上一步的基础上用2.3.5.7乘出最小的丑数
  子问题:如何求出上一步的丑数
2.转移方程:dp[i]表示第i个丑数
(dp[a]表第2的次方个的丑数,dp[b]表第3的次方个丑数,dp[c]表第5的次方个丑数,dp[d]表第7的次方个丑数)
  dp[i] = min(dp[a] * 2, min(dp[b] * 3, min(dp[c] * 5, dp[d] * 7)));//其中a.b.c.d都是随着上一步不断改变以保证不会重复
3.初始条件:dp[1]=1//1为第一个丑数
4.计算顺序:跳跃前进,每次都选择出哪个因数的第几次方是当前最小的可选的
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
using namespace std;
int dp[5843];
void solve()
{
    
    
    mm(dp, 0);
    int a, b, c, d;
    a = b = c = d = 1;//初始为1,每次选择完最小的都将该字符加1,保证下一次最小的不会是它自己
    dp[1] = 1;
    rep1(i, 2, 5842)//打表
    {
    
    
        dp[i] = min(dp[a] * 2, min(dp[b] * 3, min(dp[c] * 5, dp[d] * 7)));
        //下面为确定这一个数是由第几个丑数变换来的
        if (dp[i] == dp[a] * 2)
            ++a;
        if (dp[i] == dp[b] * 3)
            ++b;
        if (dp[i] == dp[c] * 5)
            ++c;
        if (dp[i] == dp[d] * 7)
            ++d;
    }
}
int main()
{
    
    
    int n;
    solve();
    while (~scanf("%d", &n), n)
    {
    
    
        printf("The %d", n);
        if(n%10==1&&n%100!=11)
            printf("st ");
        else if(n%10==2&&n%100!=12)
            printf("nd ");
        else if(n%10==3&&n%100!=13)
            printf("rd ");
        else
            printf("th ");
        printf("humble number is %d.\n", dp[n]);
    }
    return 0;
}

搬寝室

HDUOJ测试链接

题目描述:
有个人有n件行李,每个行李都有自己的重量a_i,这个人有两条胳膊,每条胳膊可以各搬一件,他想计算自己搬完这k趟行李后的最低疲劳度(疲劳度=(左右手重量差)2)

输入描述:
每组输入数据有两行,第一行有两个数n,k(2<=2*k<=n<2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数).

输出描述:
对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.

样例:
输入:
2 1
1 3
输出:
4

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.确定状态:
  最后一步:这件行李(第i件)拿不拿
  子问题:前i-1件物品最优疲劳度是多少
2.转移方程:dp[i][j]表示前i件物品拿j次的最低疲劳度
  dp[i][j]=min(dp[i-1][j],dp[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]))
  //若不拿,就是前i-1件拿了j次的最优解就是前者。若拿,就是前i-2件拿了k次的最优解加上拿这两件相邻的物品的疲劳度就是后者
  //两者比较择最优
3.初始化:dp要为最大int来维护最小值,下标0为边界化为0
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
const int int_max = 1 << 31 - 1;
using namespace std;
int a[2005], dp[2005][1200];//dp[i][j]表示从前i个物品中选j对
int main()
{
    
    
    int n, k;
    while(~scanf("%d%d",&n,&k))
    {
    
    
        mm(dp, 0);
        mm(a, 0);
        rep1(i, 1, n) cin >> a[i];
        sort(a+1, a + n+1);
        rep1(i, 1, n) rep1(j, 1, k) dp[i][j] = int_max;
        rep1(i, 2, n) rep1(j, 1, i / 2) dp[i][j] = min(dp[i-1][j], dp[i - 2][j - 1] + (a[i] - a[i - 1]) * (a[i] - a[i - 1]));
        cout << dp[n][k] << endl;
    }
}

不同的路径(进阶版)

LeetCode测试链接

题目描述
有一个机器人的位于一个 m × n 个网格左上角。
机器人每一时刻只能向下或者向右移动一步。
但网格有自己的绘制,只有为0的地方能移动,为1的地方是障碍
机器人试图达到网格的右下角。
问有多少条不同的路径?
(m<=100,n<=100)

样例:
样例1
在这里插入图片描述
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
向右 -> 向右 -> 向下 -> 向下
向下 -> 向下 -> 向右 -> 向右

样例2
在这里插入图片描述
输入:obstacleGrid = [[0,1],[0,0]]
输出:1

样例3
输入:[[1,0],[0,0]]
输出:0

样例4
输入:[[0,0],[1,1],[0,0]]
输出:0

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
状态与方程和入门题中的“不同的走法”类似,只需要正确判断边界遇障碍的情况即可
class Solution {
    
    
public:
    int uniquePathsWithObstacles(vector<vector<int>>& map) {
    
    
        int n=map.size();
        int m=map[0].size();

        int dp[110][110];
        //上左两边初始化,若遇障碍之后的走法全为0
        for(int i=0;i<n;i++)
        {
    
    
            if(map[i][0]==1)
            {
    
    
                for(;i<n;i++) dp[i][0]=0;
                break;
            }
            dp[i][0]=1;
        }
        for(int i=0;i<m;i++)
        {
    
    
            if(map[0][i]==1)
            {
    
    
                for(;i<m;i++) dp[0][i]=0;
                break;
            }
            dp[0][i]=1;
        }
        
        //内部dp递推开始,有障碍的地方走法为0
        for(int i=1;i<n;i++)
        {
    
    
            for(int j=1;j<m;j++)
            {
    
    
                if(map[i][j]==1) dp[i][j]=0;
                else dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }

        return dp[n-1][m-1];//输出最后一格走法
    }
};

过河卒

洛谷测试链接

题目描述:
棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A 点 (0,0),B 点 (n,m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
跳马规则:马走日(11为可走点,__为不可走点)

__ 11 __ 11 __
11 __ __ __ 11
__ __ __ __
11 __ __ __ 11
__ 11 __ 11 __

输入描述:
一行四个正整数,分别表示 B 点坐标和马的坐标。

输出描述:
一个整数,表示所有的路径条数。

样例
输入
6 6 3 3
输出
6

数据范围:
对于 100% 的数据,1≤n,m≤20,0≤马的坐标 ≤20。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
跟上题一样
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <queue>
//#define rep1(i, a, n) for (int i = a; i <= n; i++)
//#define rep2(i, a, n) for (int i = a; i >= n; i--)
//#define mm(a, b) memset(a, b, sizeof(a))
//#define elif else if
typedef long long ll;
const int INF = 0x7FFFFFFF;
using namespace std;
int xHorse, yHorse;//马坐标
int xB, yB;//B坐标

bool check(int x, int y)//检查能否通过的函数
{
    
    
    int dx[8] = {
    
    -1, 1, -2, 2, -2, 2, -1, 1};//移动方位(日字)
    int dy[8] = {
    
    2, -2, 1, -1, -1, 1, -2, 2};
    if(x==xHorse&&y==yHorse)//别漏判这个
        return false;
    for (int i = 0; i < 8; i++)
    {
    
    
        if(x==dx[i]+xHorse&&y==dy[i]+yHorse)
            return false;
    }
    return true;
}
int main()
{
    
    
    ll dp[50][50];//数据有可能很大,开longlong
    scanf("%d%d%d%d", &xB, &yB, &xHorse, &yHorse);
    
    //初始上左两边为1
    for (int i = 0; i <= xB; i++)
    {
    
    
        if(!check(i,0))//碰上不能通过的后面全部清零
        {
    
    
            for (; i <= xB;i++)
                dp[i][0] = 0;
        }
        else
            dp[i][0] = 1;
    }
    for (int i = 0; i <= yB;i++)
    {
    
    
        if(!check(0,i))
        {
    
    
            for (; i <= yB;i++)
                dp[0][i] = 0;
        }
        else
            dp[0][i] = 1;
    }

    //递推部分
    for (int i = 1; i <= xB;i++)
    {
    
    
        for (int j = 1; j <= yB;j++)
        {
    
    
            if(check(i,j))
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            else//不能存在的点设为0
                dp[i][j] = 0;
        }
    }
    printf("%lld", dp[xB][yB]);
    return 0;
}

由于dp较难掌握且要刷很多题才能彻底掌握,就算发布别的博客这篇也会不断更新

DP巨头算法

下面均是我的博客

背包问题

传送门

最短路问题

传送门

猜你喜欢

转载自blog.csdn.net/SnopzYz/article/details/113361739