【蓝桥杯】线性DP专题


DP专题

一、线性DP——数字三角形模型

1~5题具体请看: 动态规划——数字三角形模型(线性DP)_塔塔开!!!的博客-优快云博客

1.数字三角形

【代码实现】

注意看题目:−10000≤三角形中的整数≤10000

所有要初始化边界!!!

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;
int a[N][N];
int f[N][N];
int n;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= i; j ++)
            cin >> a[i][j];
    // 初始化边界        
    for (int i = 0; i <= n; i ++ ) 
        for (int j = 0; j <= i + 1; j ++ )// 注:j 从 0 到 i + 1(下一层最后一个位置上放的状态来自此时i + 1)
            f[i][j] = -INF;   
    
    f[1][1] = a[1][1];
    for(int i = 2; i <= n; i ++)
        for (int j = 1; j <= i; j ++ )
        f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j];
    // 遍历最后一层求最大值
    
    int res = -INF;
    for(int j = 1; j <= n; j ++)
        res = max(res, f[n][j]);
    cout << res;    
    
    
    return 0;
}

DFS深搜:

从起点深向下或者向右下搜到最后一层求解最大和,毫无疑问会超时

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;
int a[N][N];
int f[N][N];
int n;
int ans = -INF;

void dfs(int x, int y, int sum)
{
    // 递归出口
    if(x > n)
    {
        ans = max(ans, sum);
        return;
    }
    // 向下走或者右下走
    dfs(x + 1, y, sum + a[x + 1][y]);
    dfs(x + 1, y + 1, sum + a[x + 1][y + 1]);
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= i; j ++)
            cin >> a[i][j];
    
    dfs(1, 1, a[1][1]);
    cout << ans;
    
    
    return 0;
}

记忆化搜索(DP的另一种实现方式)

  • 求解每一个点的值,先判断该点的值是否曾经求解过,如果曾经求解过,直接拿过来使用;如果没求解过,递归求解,并存储该解!
  • 将计算过的值存储到一个数组中(当用到时直接取用,避免大量重复计算)
  • 如何判断是否求解过呢?——做标记判断

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;
int a[N][N];
int f[N][N];// 存储记录计算过的结果
int n;

int dfs(int x, int y)
{
    // 最后一层答案即为自己(递归的初始必要已知条件)
    if(x == n) return f[x][y] = a[x][y];
    // 之前计算过了,直接用
    if(f[x][y] != -1) return f[x][y];
    
    //求解(x,y)点走到底层经过的数字和的最大值,并存储
    int t = a[x][y] + max(dfs(x + 1, y), dfs(x + 1, y + 1));
    return f[x][y] = t;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= i; j ++)
            cin >> a[i][j];
    
    memset(f, -1, sizeof f);// 标记
    int ans = dfs(1, 1);
    cout << ans;
    
    
    return 0;
}

2.摘花生

3.最低通行费

4.方格取数

5.地宫取宝

6.蓝桥杯 数字三角形

【题目链接】数字三角形 - 蓝桥云课 (lanqiao.cn)

本题的限制:

数字三角形变形,多了限制条件,向左下走的次数与向右下走的次数相差不能超过 1。

DFS深搜:

我们可以统计向下和向右下走的次数,在递归出口特判即可。

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;
int a[N][N];
int n;
int ans;

// l_cnt:往左下走的次数
// r_cnt:往右下走的次数
void dfs(int x, int y, int l_cnt, int r_cnt, int sum)
{
    if(x == n)
    {
        if(abs(l_cnt - r_cnt) <= 1)
            ans = max(ans, sum);
        return ;
    }
    dfs(x + 1, y, l_cnt + 1, r_cnt, sum + a[x + 1][y]);
    dfs(x + 1, y + 1, l_cnt, r_cnt + 1, sum + a[x + 1][y + 1]);
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            cin >> a[i][j];
            
    dfs(1, 1, 0, 0, a[1][1]);
    cout << ans;
    return 0;
}

DP:

数字三角形上的数都是 0 至 100 之间的整数。

因此可以不用初始化边界为-INF

向左下走的次数与向右下走的次数相差不能超过 1,这个条件如何满足?

不妨进行模拟,在满足条件下行走的方案情况:

n为奇数时:在满足条件的情况下,最后一步必定落在位置:[n][n / 2 + 1]

n为偶数时:在满足条件的情况下,最后一步必定落在位置:[n][n / 2]或者[n][n / 2 + 1]

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;
int f[N][N];
int a[N][N];
int n;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= i; j ++)
            cin >> a[i][j];
            
    f[1][1] = a[1][1];     
    for(int i = 2; i <= n; i ++)
        for(int j = 1; j <= i; j ++)
            f[i][j] = max(f[i - 1][j], f[i - 1][j - 1]) + a[i][j];
    
    int res = -INF;        
    if(n % 2 == 0)
        res = max(f[n][n / 2], f[n][n / 2 + 1]);
    else
        res = f[n][n / 2 + 1];
        
    cout << res;    
    
    return 0;
}

7.蓝桥杯 跳跃

【题目链接】跳跃 - 蓝桥云课 (lanqiao.cn)

dfs深搜:

本题限制:他不能走到行号比 r 小的行,也不能走到列号比 c 小的列。同时,他一步走的直线距离不超过 3。即他每一次跳跃可以选择多种不同跳跃方式从而到达某一可行点!

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;
int w[N][N];
int n, m;

int ans;

int dx[6] = {0, 0, 0, 1, 2, 3};
int dy[6] = {1, 2, 3, 0, 0, 0};

void dfs(int x, int y, int sum)
{
    sum += w[x][y];
    if(x == n && y == m)
    {
        ans = max(ans, sum);
        return ;
    }
    // 枚举所有可能情况
    for(int i = 0; i < 6; i ++)
    {
        int a = x + dx[i], b = y + dy[i];
        if(a <= n && y <= m)
            dfs(a, b, sum);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            cin >> w[i][j];
       
    dfs(1, 1, 0);
    cout << ans;
    return 0;
}

DP:

状态表示:
g[i][j] 表示到达此点的最大权值。
状态转移方程:

(a,b)表示由上一个点(i,j)到此时(a,b)点的多种可能情况

f[a][b] = max(f[a][b], f[i][j] + w[a][b])表示求能够到达此点的所有点的权值最大值。

注:这题与第一、二的明显区别是,从起点走到(a,b)点而不再是从起点走到(i,j)点

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;
int w[N][N];
int f[N][N];
int n, m;

int ans;

int dx[6] = {0, 0, 0, 1, 2, 3};
int dy[6] = {1, 2, 3, 0, 0, 0};

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            cin >> w[i][j];
    
    memset(f, -0x3f, sizeof f);  
    f[1][1] = w[1][1];        
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            for(int k = 0; k < 6; k ++)
            {
                int a = i + dx[k];
                int b = j + dy[k];
                if(a >= 1 && a <= n && b >= 1 && b <= m)
                {
                    f[a][b] = max(f[a][b], f[i][j] + w[a][b]);
                }
            }
    cout << f[n][m];        
       
    return 0;
}

二、线性DP——LIS模型

1.最长上升子序列

【题目链接】895. 最长上升子序列 - AcWing题库

【代码实现】

时间复杂度:O(n * n)

#include<iostream>

using namespace std;

const int N = 1010;
int f[N];// f[i]:以a[i]为结尾的所有子序列的集合
int a[N];
int n;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    int res = 0;
    for(int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for(int j = 1; j < i; j ++)
        {
            if(a[i] > a[j])
                f[i] = max(f[i], f[j] + 1);
        }
        res = max(res, f[i]);
    }
    cout << res;
    return 0;
}

2.最长上升子序列 II

【题目链接】896. 最长上升子序列 II - AcWing题库

【代码实现】

思路:贪心 + 二分

时间复杂度:O(nlongn)

#include<iostream>

using namespace std;

const int N = 1e5 + 10;
int f[N];// 存放上升子序列长度为i的上升子序列的末尾数值最小为几
int a[N];
int n;
int len;

int find(int x)
{
    int l = 1, r = len;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(f[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    f[1] = a[1];
    len = 1;
    for(int i = 2; i <= n; i ++)
    {
        if(a[i] > f[len]) f[++ len] = a[i];// 如果a[i]大于f[]中的最后一个数,直接拼接,长度加1
        else
        {
            int idx = find(a[i]);// 在f[]数组中找到第一个大于等于a[i]的数的位置
            f[idx] = a[i];// 然后替换
        }
    }
    cout << len;
    return 0;
}

3.怪盗基德的滑翔翼

【题目链接】1017. 怪盗基德的滑翔翼 - AcWing题库

【代码实现】

倒V形某一边的最长上升子序列。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;
int f[N];
int a[N];


int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i ++ ) cin >> a[i];
        
        int res = 0;
        for (int i = 1; i <= n; i ++ )
        {
            f[i] = 1;
            for(int j = 1; j < i; j ++)
                if(a[i] > a[j])
                    f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }    
        
        memset(f, 0, sizeof f);
        for(int i = n; i >= 1; i --)
        {
            f[i] = 1;
            for(int j = n; j > i; j --)
                if(a[i] > a[j])
                    f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }
        
        cout << res << endl;    
    }
    
    return 0;
}

4.登山

【题目链接】1014. 登山 - AcWing题库

倒V形两边的上升子序列的最大(和)。

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N];
int g[N];
int a[N];
int n;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    
    for (int i = 1; i <= n; i ++ )
    {
        f[i] = 1;
        for(int j = 1; j < i; j ++)
            if(a[i] > a[j])
                f[i] = max(f[i], f[j] + 1);
    }
    
    for(int i = n; i >= 1; i --)
    {
        g[i] = 1;
        for(int j = n; j > i; j --)
            if(a[i] > a[j])
                g[i] = max(g[i], g[j] + 1);
    }
    
    int res = 0;
    for(int i = 1; i <= n; i ++) res = max(res, f[i] + g[i] - 1);// 记得减去重合的顶点
    cout << res;
    
    return 0;
}

5.合唱队形

【题目链接】482. 合唱队形 - AcWing题库

思路:求最长上升子序列的扩展应用题,以每一个数为中心,求取它各个数的左右两边的最长递增子序列是多少,剩下的就是要筛掉的最少人数。

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;
int a[N];
int f[N], g[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    
    int res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        f[i] = 1;
        for(int j = 1; j < i; j ++)
            if(a[i] > a[j])
                f[i] = max(f[i], f[j] + 1);
    }

    for(int i = n; i >= 1; i -- )
    {
        g[i] = 1;
        for(int j = n; j > i; j --)
            if(a[i] > a[j])
                g[i] = max(g[i], g[j] + 1);
    }
    
    for (int i = 1; i <= n; i ++ ) res = max(res, f[i] + g[i] - 1);
    cout <<n - res;
    
    return 0;
}

6.最大上升子序列和

【题目链接】1016. 最大上升子序列和 - AcWing题库

f[i] 表示前i个数中的最大子序列和

状态转移:
对于每一个小于a[i]a[j] (j < i)
f[i] = max(f[i], f[j] + a[i])

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int a[N];
int f[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    
    int res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        f[i] = a[i];
        for (int j = 1; j < i; j ++ )
            if (a[i] > a[j])
                f[i] = max(f[i], f[j] + a[i]);
        res = max(res, f[i]);
    }
    cout << res;
    
    return 0;
}

7.蓝桥杯 游园安排

三、线性DP——LCS模型

1.最长公共子序列

【题目链接】897. 最长公共子序列 - AcWing题库

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N][N];
char a[N], b[N];
int n, m;
// f[i][j]:a序列中前i个数与b序列中前j个数的公共序列的所有集合
// 属性:max

int main()
{
    cin >> n >> m;
    cin >> a + 1 >> b + 1;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1;
            else
            {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            }
        }
    cout << f[n][m];
    
    return 0;
}

2.蓝桥杯 密码脱落

【题目链接】密码脱落 - 蓝桥云课 (lanqiao.cn)

思路:将原串翻转,然后求原串与新串的最长公共子序列,那么剩下的即为要脱落的个数。

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;


const int N = 1010;
int f[N][N];
char a[N], b[N];

int main()
{
    cin >> a + 1;
    int n = strlen(a + 1);
    for(int i = 1, j = n; i <= n; i ++, j --) b[i] = a[j];// 翻转
    
    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= n; j ++)
        {
            if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1;
            else
            {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            }
        }
    cout << n - f[n][n];    
    return 0;
}

3.蓝桥杯 蓝肽子序列

【题目链接】蓝肽子序列 - 蓝桥云课 (lanqiao.cn)

思路:

最最长公共子序列的长度,但元素不再是单个元素,而是由字符串看成一个元素,预处理出来然后LCS即可。

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N][N];
string a[N], b[N];
int cnt1, cnt2;


int main()
{
    string s1, s2;
    cin >> s1;
    cin >> s2;
    int len1 = s1.size(), len2 = s2.size();
    
    // 预处理字符串————使得每一个子钛为一个元素
    for (int i = 0; i < len1; i ++ )
    {
        
        if(s1[i] >= 'A' && s1[i] <= 'Z')
        {
            int j = i + 1;
            while(s1[j] >= 'a' && s1[j] <= 'z') j ++;
            string str = s1.substr(i, j - i);
            a[++ cnt1] = str;
        }
        
    }
    for (int i = 0; i < len2; i ++ )
    {
        
        if(s2[i] >= 'A' && s2[i] <= 'Z')
        {
            int j = i + 1;
            while(s2[j] >= 'a' && s2[j] <= 'z') j ++;
            string str = s2.substr(i, j - i);
            b[++ cnt2] = str;
        }
        
    }    
    // for(int i = 1; i <= cnt1; i ++) cout << a[i] << ' ';
    // puts("");
    // for(int i = 1; i <= cnt2; i ++) cout << b[i] << ' ';
    
    
    // 求a数组与b数组的LCS
    for (int i = 1; i <= cnt1; i ++ )
        for (int j = 0; j <= cnt2; j ++ )
        {
            if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1;
            else
            {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            }
        }
    cout << f[cnt1][cnt2];    
    
    return 0;
}

四、其它

1.最短编辑距离

【题目链接】902. 最短编辑距离 - AcWing题库

在这里插入图片描述

思路:

在这里插入图片描述

有三个操作,因此有三个子集!

状态表示 dp[i][j]

  • 集合 : 所有吧a中的前i个字母 变成 b中前j个字母的集合的操作集合
  • 属性 : 所有操作中操作次数最少的方案的操作数

状态计算
状态划分 以对a中的第i个字母操作不同划分

  • 在该字母之后添加

    • 添加一个字母之后变得相同,说明没有添加前a的前i个已经和b的前j-1个已经相同
    • 即 : dp[i][j] = dp[i][j-1] + 1
  • 删除该字母

    • 删除该字母之后变得相同,说明没有删除前a中前i-1已经和b的前j个已经相同
    • 即 : dp[i][j] = dp[i-1][j] + 1
  • 替换该字母

    • 替换存在两种情况:结尾字母不同;结尾字母相同;
      • a[i] == b[j],啥也不做,即: dp[i][j] = dp[i-1][j-1]
      • 若应结尾字母不相同,直接替换即可
        即:dp[i][j] = dp[i-1][j-1] + 1

时间复杂度:O(n*n)

【代码实现】

#include <iostream>
#include <cstring>
#include<cstdio>
#include <algorithm>

using namespace std;

const int N = 1010;
int dp[N][N];// dp[i][j] :a中1~i与b中1~j相等需要的最少操作次数
char a[N], b[N];
int n, m;
int main()
{
    scanf("%d%s", &n, a + 1);// 字符串从下标1开始输入
    scanf("%d%s", &m, b + 1);
    
    //边界:当只有字符串b或者a时,只能通过插入操作完成
    for(int i = 0; i <= m; i ++) dp[0][i] = i;
    for(int i = 0; i <= n; i ++) dp[i][0] = i;
    
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            {
                dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                if(a[i] == b[j]) dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
                else dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + 1);
            }
    printf("%d", dp[n][m]);
    return 0;
}

参考文献:

1.acwing算法基础课、提高课

2.蓝桥杯题库

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值