5.动态规划

news2024/10/7 14:23:19

1.背包问题

(1)0/1背包问题

        01背包问题即每个物品只能选1个

        考虑第i件物品,当j<w[i]时,f[i][j]=f[i-1][j],当j>=w[i]时,此时有两种选择,选择第i件物品和不选第i件物品。此时f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i])

        有 n件物品和一个容量是m的背包。每件物品只能使用一次,第i件物品的体积是vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大.输出最大价值。 

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;
int w[N],v[N];
int f[N][N];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j] = f[i-1][j];
            if(j>=w[i]) f[i][j] = max(f[i][j],f[i-1][j-w[i]]+v[i]);
        } 
    }
    printf("%d",f[n][m]);
    return 0;
    
}

 (2)完全背包问题

        完全背包问题即物品可以选多个

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;
int w[N],v[N];
int f[N][N];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j] = f[i-1][j];  // 注意选择物品下标为i,区别于01背包
            if(j>=w[i]) f[i][j] = max(f[i][j],f[i][j-w[i]]+v[i]);
    }
    }
    printf("%d",f[n][m]);
    return 0;
    
}

 (3) 多重背包问题

        多重背包问题可以转化为0-1背包问题进行优化。假设第i个物品可选c个,那么我们可以用二进制组合为0-c范围的数。此时,每一位二进制可视为一件物品。

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e3+10;
const int V = 110;
int f[N][V];
int v[N],w[N];
int cnt = 0;

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    int a,b,c,k;
    while (n--)
    {
        scanf("%d%d%d",&a,&b,&c);
        k = 1;
        while (k<=c)  // 将c用二进制进行拆分
        {
            cnt++;
            w[cnt] = k*a;
            v[cnt] = k*b;
            c -= k;
            k *=2;
        }
        if(c){  // 拆分为二进制后的剩余部分
            cnt++;
            w[cnt] = c * a;
            v[cnt] = c*b;
        }
    }
    for(int i=1;i<=cnt;i++) // 当成0-1背包问题求解
        for(int j=1;j<=m;j++){
            f[i][j]=f[i-1][j];
            if(j>=w[i]) f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]);
        }
    printf("%d",f[cnt][m]);
    return 0;
    
}

(3)分组背包问题

        分组背包问题只需要考虑每一组选择某个物品的最大价值,只需要加一层循环。

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 110;
const int V = 110;
const int S = 110;

int f[N][V];  // DP表
int w[N][S];  // 重量
int v[N][S];  // 价值
int G[N];     // 每一组物品种类数

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&G[i]); 
        for(int j=1;j<=G[i];j++)
            scanf("%d%d",&w[i][j],&v[i][j]);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j] = f[i-1][j];
            for(int k=1;k<=G[i];k++){ // 遍历每一组
                if(j>=w[i][k]) f[i][j] = max(f[i][j],f[i-1][j-w[i][k]]+v[i][k]);
            }
        }
    printf("%d",f[n][m]);
    return 0;
}

2.线性DP

     (1)数字三角形

        给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

    算法思想:对于每一个i,j,都有两条路径,即从左到右的一条与从左上角到右的一条。因此

f[i][j]=max(f[i-1][j]+a[i][j],f[i-1][j-1]+a[i][j])

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 510;
const int inf = 1e9;
int a[N][N];
int f[N][N];

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)  // 读取数字
        for(int j=1;j<=i;j++)
            scanf("%d",&a[i][j]);
    
    for(int i=0;i<=n;i++)  // 初始化矩阵
        for(int j=0;j<=n;j++)
            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]+a[i][j],f[i-1][j]+a[i][j]);

    int res = -inf;
    for(int i=1;i<=n;i++)
        res = max(f[n][i],res);
    printf("%d",res);
    return 0;
}

 (2) 最长上升子序列

        给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少

        对于每一个f[i],如果前面有更小的数,f[i]=max(f[i],f[j]+1)。

#include <iostream>
using namespace std;

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

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        f[i] = 1;   // a[i]前面没有最小的数
        for(int j=1;j<i;j++) // 如果a[i]前面有更小的数,则更新
            if(a[j]<a[i]) f[i] = max(f[i],f[j]+1);
    }
    int res = 0;
    for(int i=1;i<=n;i++) res = max(res,f[i]);
    printf("%d",res);
    return 0;
}

 (3)最长公共子序列

        当匹配到A[i]和B[j]时,有4种情况,如下图所示

        其中,第一种情况包含在第2,3种情况中,第4种情况需要A[i]与B[j]匹配成功

给定两个长度分别为 N 和 M 的字符串 和 B,求既是 A的子序列又是 B 的子序列的字符长度最长是多少
输入格式
第一行包含两个整数 N和 M
第二行包含一个长度为 N的字符串,表示字符串 A
第三行包含一个长度为 M 的字符串,表示字符串 B
字符串均由小写字母构成。 

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1010;
const int M = 1010;
char A[N],B[M];
int f[N][M];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    scanf("%s%s",A+1,B+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j] = max(f[i-1][j],f[i][j-1]); // 子串A[i-1]与B[j]匹配或者A[i]与B[j-1]匹配
            if(A[i]==B[j]) f[i][j] = max(f[i][j],f[i-1][j-1]+1); // 子串A[i]与B[j]匹配且匹配成功
        }
    printf("%d",f[n][m]);
}

(4)最短编辑距离

增删改情况如下图

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;
const int M = 1010;
char A[N],B[M];
int f[N][M];

int main(){
    int n,m;
    scanf("%d%s",&n,A+1);
    scanf("%d%s",&m,B+1);
    for(int i=0;i<=n;i++) f[i][0] = i; // 边界条件,初始化
    for(int j=0;j<=m;j++) f[0][j] = j;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j] = min(f[i-1][j]+1,f[i][j-1]+1); // 增与删
            if(A[i]==B[j]) f[i][j] = min(f[i][j],f[i-1][j-1]); // A[i]与B[j]匹配成功
            else f[i][j] = min(f[i][j],f[i-1][j-1]+1);  // A[i]与B[j]匹配不成功
        }
    printf("%d",f[n][m]);
    return 0;
}

应用:

给定 n 个长度不超过 10 的字符串以及 m 次询问,每次问给出一个字符串和一个操作次数上限.
对于每次询问,请你求出给定的n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。

算法思想:多次求编辑距离

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 1010;
int f[15][15];
char str[N][15];

int edit_dist(char a[],char b[]){
    int la = strlen(a+1);
    int lb = strlen(b+1);
    for(int i=0;i<=la;i++) f[i][0] = i;  // 边界情况
    for(int i=0;i<=lb;i++) f[0][i] = i;
    for(int i=1;i<=la;i++)
        for(int j=1;j<=lb;j++){
            f[i][j] = min(f[i][j-1]+1,f[i-1][j]+1);  // 增和删
            if(a[i]==b[j]) f[i][j] = min(f[i][j],f[i-1][j-1]); // 匹配成功
            else f[i][j] = min(f[i][j],f[i-1][j-1]+1);  // 改
        }
    return f[la][lb];
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%s",str[i]+1);
    int limit,res;
    while (m--)
    {
        char s[15];
        scanf("%s%d",s+1,&limit);
        res = 0;
        for(int i=0;i<n;i++)
            if(edit_dist(str[i],s)<=limit) res++;
        printf("%d\n",res);
    }
    return 0;
}

 3.区间类DP

        石子合并:

算法思想:动态规划加分治法。对于区间[L,R],可以从第k个位置分开,合并的代价包含三部分:

(1)合并左侧;(2)合并右侧;(3) 将左右部分合并;k的位置从[l,r]。取代价最小值。

即f[l][r]=min(f[i][k]+f[k+1][r]+s[r]-s[l-1])。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 310;
int s[N];  // 前缀和数组
int f[N][N]; // 递推数组

int main(){
    int n;    // 石子堆数
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&s[i]);
    for(int i=1;i<=n;i++) s[i] = s[i]+s[i-1];
    int l,r;
    for(int len=2;len<=n;len++)
        for(int i=1;i+len-1<=n;i++){
            l = i,r=i+len-1;
            f[l][r] = 1e8;
            for(int k=l;k<r;k++)  // 最后一次合并从节点k将区间分为两部分
                f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
        }
    printf("%d",f[1][n]);
    return 0;
}

4.整数划分

算法思想:整数划分可以采用完全背包问题解决,当j<i时,不能选i,此时f[i][j]=f[i-1][j],当j>=i时,f[i][j] = f[i-1][j] + f[i][j-i](注意此时求的是方案数)

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;
const int M = 1e9+7;
int f[N][N];

int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<=n;i++)
        f[i][0] = 1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            f[i][j] = f[i-1][j];  // j<i,不能选i
            if(j>=i)  // j>i,可以选i
                f[i][j] = (f[i-1][j] + f[i][j-i]) % M;
        }
    printf("%d",f[n][n]);
    return 0;
}

5.状态压缩DP 

多米诺骨牌问题

首先,如果全部用横条填充完整个棋盘,剩下的用竖条填充,方案是确定的。

使用二进制表示每一列的填充情况。当第i-1与第i列两列没有被横向网格填充,且存在两个连续的网格接收横向网格,则填充。此时:f[i][j] += f[i-1][k]。即累加第i-1行的填充结果

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

using namespace std;

const int N = 12, M = 1 << N;

int n, m;
long long f[N][M];
bool st[M];

int main()
{
    while (1)
    {
        scanf("%d%d",&n,&m);
        if(n ==0 || m==0) break;
        for(int i=0;i< 1<<n;i++){
            int cnt = 0;
            st[i] = 1;
            for(int j=0;j<n;j++)
                if(i >>j &1){     // 该位为1
                    if(cnt & 1) st[i] = 0;  // 连续奇数个1
                    cnt = 0;
                }
                else cnt++;    // 连续0的个数
            if(cnt & 1) st[i] = 0;  // 最后有奇数个1
        }

        memset(f, 0, sizeof f);
        memset(f, 0, sizeof f);
        f[0][0] = 1; // 一种方案,不填
        for(int i=1;i<=m;i++)
            for(int j=0;j< 1<<n; j++)
                for(int k=0;k< 1<<n; k++)
                    if((j & k)==0 && st[j|k]) f[i][j] += f[i-1][k]; // 意味着没有一列是两行都被填充的且能够找到两列都没有被填充的

        cout << f[m][0] << endl;
    }
    return 0;
}

哈密尔顿通路:

给定一张n 个点的带权无向图,点从0~ n -1标号,求起点0到终点n -1的最短 Hamilton 路径
Hamilton 路径的定义是从 0 到 n-1不重不漏地经过每个点恰好一次。

算法思想:基于动态规划的思想,对于每一次的f[i][j],表示j为当前最后一次经过的节点,首先,要走遍子集i中除去j以外的所有节点,到达节点k,然后再从第k个节点到达第j个节点,即f[i][j]=min(f[i-{j}][k]+w[k][j])

#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;

const int N = 20;
const int M = 1 << N;
int G[N][N];
int f[M][N];

int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            scanf("%d",&G[i][j]);
    memset(f,0x3f,sizeof f);
    f[1][0] = 0;   // 从0号点经过0号点到0
    for(int i=0;i< 1<<n;i++)  // 用二进制表示当前节点子集
        for(int j=0;j<n;j++){
            if(i>>j & 1)   // 当前子集包含第j个节点
                for(int k=0;k<n;k++)
                    if(i>>k &1)  // 子集i中包含节点k
                        f[i][j] = min(f[i][j],f[i-(1<<j)][k]+G[k][j]);
        }
    printf("%d",f[(1<<n)-1][n-1]);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1566105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++20协程详解(一)

前言 本文是c协程第一篇&#xff0c;主要是让大家对协程的定义&#xff0c;以及协程的执行流有一个初步的认识&#xff0c;后面还会出两篇对协程的高阶封装。 在开始正式开始协程之前&#xff0c;请务必记住&#xff0c;c协程 不是挂起当前协程&#xff0c;转而执行其他协程&a…

789. 数的范围 (二分学习)左端大右,右端小左

题目链接https://www.acwing.com/file_system/file/content/whole/index/content/4317/ 当求左端点时&#xff0c;条件是a【mid】大于等于x&#xff0c;并把右端点缩小。 当求右端点时&#xff0c;条件是a【mid】小于等于x&#xff0c;并把左端点扩大。 1.确定一个区间&…

面试复盘1 - 测试相关(实习)

写在前&#xff1a;hello&#xff0c;大家早中晚上好~这里是西西&#xff0c;最近有在准备测试相关的面试&#xff0c;特此开设了新的篇章&#xff0c;针对于面试中的问题来做一下复盘&#xff0c;会把我自己遇到的问题进行整理&#xff0c;除此之外还会进行对一些常见面试题的…

CentOSPython使用openpyxl、pandas读取excel报错

一、openpyxl报错 由于本人在centos7下使用python的openpyxl报错,所以决定使用强大的pandas。 pandas是一个快速、强大、灵活且易于使用的开源数据操作和分析工具,建立在Python语言之上。 pandas是一个广泛使用的数据分析库,它提供了高效的数据结构和数据分析工具。pandas…

new mars3d.layer.HeatLayer({实现动态修改热力图半径

1.使用热力图插件的时候&#xff0c;实现动态修改热力图效果半径 2.直接修改是不可以的&#xff0c;因为这个是热力图本身的参数。 因此我们需要拿到这个热力图对象之后&#xff0c;参考api文档&#xff0c;对整个 heatLayer.heatStyle进行传参修改。 heatStyle地址&#x…

SSL通配符证书怎么选?看这里

通配符证书&#xff0c;作为一种特殊的数字证书类型&#xff0c;以其独特的优势在网络安全领域扮演着重要角色。相较于传统的单一域名证书&#xff0c;通配符证书能够为同一主域名下的所有子域名提供安全保护&#xff0c;显著提升管理效率&#xff0c;简化证书部署流程&#xf…

【深度学习】sdwebui的token_counter,update_token_counter,如何超出77个token的限制?对提示词加权的底层实现

文章目录 前言关于token_counter关于class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing)如何超出77个token的限制&#xff1f;对提示词加权的底层实现Overcoming the 77 token limit in diffusers方法1 手动拼方法2 compel 问询、帮助请看&#xff1a; 前言 …

[已解决] slam_gmapping: undefined symbol: _ZN8GMapping14sampleGaussianEdm问题

之前用的好好的gampping建图功能包&#xff0c;今天突然不能用了&#xff0c;运行报错如下&#xff1a; /opt/ros/noetic/lib/gmapping/slam_gmapping: symbol lookup error: /opt/ros/noetic/lib/gmapping/slam_gmapping: undefined symbol: _ZN8GMapping14sampleGaussianEdm …

首场直播,就在4月11日!

2024年的第一场直播&#xff0c;我们把目光聚焦到“大会员”。 这一次我们想聊聊&#xff0c;当大会员遇上泛零售企业&#xff0c;会产生怎样的“火花”。泛零售企业突破增长压力的机会在哪里&#xff1f;又有哪些挑战必须直面&#xff1f; 本次直播将结合泛零售企业“多业态、…

双机 Cartogtapher 建图文件配置

双机cartogtapher建图 最近在做硕士毕设的最后一个实验&#xff0c;其中涉及到多机建图&#xff0c;经过调研最终采用cartographer建图算法&#xff0c;其中配置多机建图的文件有些麻烦&#xff0c;特此博客以记录 非常感谢我的同门 ”叶少“ 山上的稻草人-CSDN博客的帮助&am…

Chrome 设置在新窗口中打开链接(已登录google账号版)

Chrome的链接默认是在原标签页中打开的&#xff0c;如果要在新窗口中打开&#xff0c;需要自己自行设置&#xff0c;在此&#xff0c;针对已经登录google账号的chrome浏览器怎么进行设置进行说明。 一、点击登录图标->更多设置 二、选择其他设置->在新窗口中打开搜索结果…

Linux上管理文件系统

Linux上管理文件系统 机械硬盘 机械硬盘由多块盘片组成&#xff0c;它们都绕着主轴旋转。每块盘片上下方都有读写磁头悬浮在盘片上下方&#xff0c;它们与盘片的距离极小。在每次读写数据时盘片旋转&#xff0c;读写磁头被磁臂控制着不断的移动来读取其中的数据。 所有的盘片…

一套C#自主版权+应用案例的手麻系统源码

手术麻醉信息管理系统源码&#xff0c;自主版权应用案例的手麻系统源码 手术麻醉信息管理系统包含了患者从预约申请手术到术前、术中、术后的流程控制。手术麻醉信息管理系统主要是由监护设备数据采集子系统和麻醉临床系统两个子部分组成。包括从手术申请到手术分配&#xff0c…

SSM框架学习——JSP语法入门

JSP语法入门 前提 在前一节中我们已经写过JSP的代码了&#xff0c;这一节将单独介绍JSP一些基础语法。当然&#xff0c;你可以跳过这一节&#xff0c;当后面有代码不太理解的时候再回来阅读。 中文编码问题 如果中文乱码&#xff0c;看看JSP是否是以UTF8的方式编码&#xf…

mysql建表必须知道的18个重点(荣耀典藏版)

大家好&#xff0c;我是月夜枫&#xff0c;又和大家见面了&#xff01;&#xff01;&#xff01;&#xff01; 目录 前言 1.名字 1.1 见名知意 1.2 大小写 1.3 分隔符 1.4 表名 1.5 字段名称 1.6 索引名 2.字段类型 3.字段长度 4.字段个数 5. 主键 6.存储引擎 7.…

计算机网络:数据链路层 - 点对点协议PPP

计算机网络&#xff1a;数据链路层 - 点对点协议PPP PPP协议的帧格式透明传输字节填充法零比特填充法 差错检测循环冗余校验 对于点对点链路&#xff0c;PPP协议是目前使用最广泛的数据链路层协议。比如说&#xff0c;当用户想要接入互联网&#xff0c;就需要通过因特网服务提供…

vulnhub靶机: DC-9

dc-9靶机下载 将靶机设置为NAT模式&#xff0c;本次实验使用的内网网段为192.168.198.0/24&#xff0c;kali的ip为192.168.198.172 信息搜集 ip主机扫描&#xff1a; nmap -sP 192.168.198.0/24 确定靶机ip为192.168.198.171 主机端口扫描&#xff1a; nmap -T4 -A -v 192…

【JVM】如何定位、解决内存泄漏和溢出

目录 1.概述 2.堆溢出、内存泄定位及解决办法 2.1.示例代码 2.2.抓堆快照 2.3.分析堆快照 1.概述 常见的几种JVM内存溢出的场景如下&#xff1a; Java堆溢出&#xff1a; 错误信息: java.lang.OutOfMemoryError: Java heap space 原因&#xff1a;Java对象实例在运行时持…

简单聊聊冯诺伊曼体系结构

我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系结构。 简单聊一下&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成 中央处理器(CPU)&#xff1a; a.运算器 &#xff1a;算数运算…

配置code-server和texlive实现网页写tex

使用overleaf太卡了&#xff0c;有云服务器或者nas小主机&#xff0c;配置自己的code-servertexlive&#xff0c;来写论文。 之前用服务器配置过自己的overleaf&#xff0c;感觉不是很好用&#xff0c;缺少东西。 一、思路 使用docker先安装一个ubuntu&#xff0c;用dockerfil…