5.动态规划

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

硬件工程师职责与核心技能有哪些?

作为一个优秀的硬件工程师&#xff0c;必须要具备优秀的职业技能。那么&#xff0c;有些刚入行的工程师及在校的学生经常会问到&#xff1a;硬件工程师需要哪些核心技能&#xff1f;要回答这个问题&#xff0c;首先要明白硬件工程师的职责&#xff0c;然后才能知道核心技能要求…

c语言游戏实战(7):扫雷(下)

前言&#xff1a; 扫雷是一款经典的单人益智游戏&#xff0c;它的目标是在一个方格矩阵中找出所有的地雷&#xff0c;而不触碰到任何一颗地雷。在计算机编程领域&#xff0c;扫雷也是一个非常受欢迎的项目&#xff0c;因为它涉及到许多重要的编程概念&#xff0c;如数组、循环…

E-魔法猫咪(遇到过的题,做个笔记)

题解&#xff1a; 来自学长们思路&#xff1a; 其中一种正解是写单调队列。限制队列内的数单调递增&#xff0c;方法为每当新来的数据比当前队尾数据小时队 尾出列&#xff0c;直到能够插入当前值&#xff0c;这保证了队头永远是最小值。因此总体思路是队尾不断插入新值的同时 …

C++函数匹配机制

函数匹配 在大多数情况下&#xff0c;我们容易确定某次调用应该选用哪个重载函数。 然而&#xff0c;当几个重载函数的形参数量相等以及某些形参的类型可以由其他类型转换得来时&#xff0c;这项工作就不那么容易了。 以下面这组函数及其调用为例&#xff1a; void f(); vo…

【介绍什么是DDOS】

&#x1f308;个人主页:程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

ShareX 截图工具详细使用心得

一、软件介绍 ShareX是一款免费的开源程序。不仅可以截图&#xff0c;还可以录屏&#xff0c;自动添加水印和阴影&#xff0c;除此之外&#xff0c;还有很多很多&#xff0c;比如OCR识别、屏幕录制、颜色拾取、哈希检查、修改DNS、尺子功能、显示器测试等等。 二、下载安装 …

C++——list类及其模拟实现

前言&#xff1a;这篇文章我们继续进行C容器类的分享——list&#xff0c;也就是数据结构中的链表&#xff0c;而且是带头双向循环链表。 一.基本框架 namespace Mylist {template<class T>//定义节点struct ListNode{ListNode<T>* _next;ListNode<T>* _pre…

Stable Diffusion扩散模型推导公式的基础知识

文章目录 1、独立事件的条件概率2、贝叶斯公式、先验概率、后验概率、似然、证据3、马尔可夫链4、正态分布 / 高斯分布5、重参数化技巧6、期望7、KL散度 、高斯分布的KL散度8、极大似然估计9、ELBO :Evidence Lower Bound10、一元二次方程 1、独立事件的条件概率 A 和 B 是两个…

Rredis缓存常见面试题

文章目录 1.什么是缓存穿透&#xff0c;怎么解决2.什么是缓存击穿&#xff0c;怎么解决3.什么是缓存雪崩&#xff0c;怎么解决4.双写一致性问题5.redisson添加的排他锁是如何保证读写、读读互斥的6.为什么不使用延迟双删7.redis做为缓存&#xff0c;数据的持久化是怎么做的8.re…

【热门话题】文言一心与ChatGPT-4:一场跨时代智能对话系统的深度比较

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 文言一心与ChatGPT-4&#xff1a;一场跨时代智能对话系统的深度比较一、技术背景…

成绩管理系统|基于springboot成绩管理系统的设计与实现(附项目源码+论文)

基于springboot成绩管理系统的设计与实现 一、摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装毕业设计成绩管…

HarmonyOS应用开发ArkUI(TS)电商项目实战

项目介绍 本项目基于 HarmonyOS 的ArkUI框架TS扩展的声明式开发范式&#xff0c;关于语法和概念直接看官网官方文档地址&#xff1a;基于TS扩展的声明式开发范式&#xff0c; 工具版本&#xff1a; DevEco Studio 3.1 Canary1 SDK版本&#xff1a; 3.1.9.7&#xff08;API V…

海外媒体软文发稿:带动海外宣发新潮流,迈向国际舞台

引言 随着全球化的发展&#xff0c;越来越多的中国企业希望在国际舞台上展示自己的实力。而海外媒体软文发稿作为一种全新的海外宣传方式&#xff0c;正逐渐成为带动海外宣发新潮流的有力工具。本文将探讨海外媒体软文发稿的优势和如何迈向国际舞台。 海外媒体软文发稿的优势…

代码随想录阅读笔记-二叉树【最大二叉树】

题目 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构建最大二叉树&#x…

linux系统负载对系统的意义

负载平均值的含义 负载平均值是通过uptime和top命令显示的三个数字&#xff0c;分别代表不同时间段的平均负载&#xff08;1分钟、5分钟和15分钟的平均值&#xff09;。这三个数字越低越好&#xff0c;较高的数字意味着系统可能存在问题或过载。然而&#xff0c;并没有一个固定…

男生穿什么裤子最帅?必备的男生裤子推荐

每个人都想拥有很多条好看质量又好的裤子。不过市面上有太多服装品牌&#xff0c;甚至还有不少劣质的衣裤&#xff0c;穿洗两遍之后就出现松垮、变形的情况。为了能够让大家可以选到合适的衣裤&#xff0c;我自费购买了多个品牌的裤子&#xff0c;并给出大家测评结果。 购买到质…

网站访问502,网站服务器崩溃,比较常见几个的原因

其实&#xff0c;配置再好的服务器也难免在使用过程中出现一些故障&#xff0c;造成宕机。 服务器一旦出现故障&#xff0c;影响到用户实时访问网站&#xff0c;造成用户流失&#xff0c;如果在企业的销售高峰期&#xff0c;则将直接影响到商业利润&#xff0c;而且不仅影响外…

SD-WAN降低网络运维难度的三大关键技术

企业网络作为现代企业不可或缺的基础设施&#xff0c;承担着连接全球的重要任务。随着全球化和数字化转型的加速推进&#xff0c;企业面临着越来越多的网络挑战和压力。传统的网络组网方式已经不能满足企业规模扩大、分支机构增多、上云服务等需求&#xff0c;导致了网络性能下…

消除歧义:利用动态上下文提出有效的RAG问题建议

原文地址&#xff1a;disambiguation-using-dynamic-context-in-crafting-effective-rag-question-suggestions 2024 年 4 月 3 日 这一策略唤起了IBM沃森率先采用的一项技术&#xff1a;消除歧义。面对用户模糊不清的输入&#xff0c;系统会提供大约五个或更少的选项供用户挑…

软件架构风格_3.以数据为中心的体系结构风格

以数据为中心的体系结构风格主要包括仓库体系结构风格和黑板体系结构风格。 1.仓库体系结构风格 仓库&#xff08;Repository&#xff09;是存储和维护数据的中心场所。在仓库风格&#xff08;见图1&#xff09;中&#xff0c;有两种不同的构件&#xff1a;中央数据结构说明当…