第5将 动态规划

本文深入探讨动态规划在解决背包问题中的应用,包括01背包、完全背包、多重背包以及分组背包问题。同时,文章还详细介绍了线性DP,如数字三角形、最长上升子序列及其优化算法,以及最长公共子序列、最短编辑距离等经典问题。通过实例解析,帮助读者理解动态规划的策略和思路。
摘要由CSDN通过智能技术生成

背包问题

01背包问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


#include<iostream>
using namespace std;

const int maxn=1010;

int n,m;

int f[maxn],v[maxn],w[maxn];

int main()
{
    cin >> n >> m;

    for(int i=1; i<=n; i++)
    {
        cin >> v[i] >> w[i];
    }

    for(int i=1; i<=n; i++)
        for(int j=m; j>=v[i]; j--)
        {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }

    cout << f[m];

    return 0;
}

完全背包问题


#include<iostream>
using namespace std;

const int maxn=1111;

int f[maxn],w[maxn],v[maxn];

int n,m;

int main()
{
    cin >> n >> m;
    for(int i=1; i<=n; i++)
    {
        cin >> v[i] >> w[i];
    }

    for(int i=1; i<=n; i++)
    {
        for(int j=v[i]; j<=m; j++)      //区别于01背包,这里就是从小到大排列
        {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }

    cout << f[m];

    return 0;

}

多重背包问题01


#include<iostream>
using namespace std;

const int maxn=111;

int n,m;

int f[maxn][maxn],v[maxn],w[maxn],s[maxn];

int main()
{
    cin >> n >> m;
    for(int i=1; i<=n; i++)
        cin >> v[i] >> w[i] >> s[i];

    for(int i=1; i<=n; i++)
        for(int j=0; j<=m; j++)
            for(int k=0; k<=s[i] && j>=v[i]*k; k++)
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

    cout << f[n][m];

    return 0;

}

多重背包问题02


#include<iostream>
using namespace std;

const int N=12010;     //0<N<1000,0<s<2000,所以看下面的循环n*log2000就约等于12000
const int M=2010;            //M只用给f,所以最大体积就是f[M]

int n,m;

int f[M],w[N],v[N];

int main()
{
    cin >> n >> m;
    int cnt=0;              //cnt从0开始,相当于是将这些值全部分开
    for(int i=1; i<=n; i++)
    {
        int a,b,s;
        cin >> a >> b >> s;
        int k=1;
        while(k<=s)
        {
            cnt++;             //cnt表示
            v[cnt]=k*a;
            w[cnt]=k*b;
            s-=k;          //先是让s-=k,最后再更新k的值
            k*=2;
        }
        if(s>0)   //如果s还有剩余
        {
            cnt++;           //cnt还是++,进行一个分组
            v[cnt]=s*a;
            w[cnt]=s*b;
        }
    }



    n=cnt;        //将n更新为cnt

    //才用01背包的方法
    for(int i=1; i<=n; i++)    
        for(int j=m; j>=v[i]; j--)
            f[j]=max(f[j],f[j-v[i]]+w[i]);

    cout << f[m];

    return 0;

}

分组背包问题


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n, m;
int v[N][N], w[N][N], s[N];
int f[N];

int main()
{
    cin >> n >> m;

    for (int i = 1; i <= n; i ++ )               //和01背包的区别在于,这里 进行了一个分组
    {
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++ )
            cin >> v[i][j] >> w[i][j];
    }

    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 0; j -- )          //这里j的值不是>=v[i],而是j>=0,因为此时v[i][k]表示第i组,第k个
            for (int k = 0; k < s[i]; k ++ )
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

    cout << f[m] << endl;       //m最后就是总体积

    return 0;
}


线性DP

数字三角形


#include<iostream>
using namespace std;

const int maxn=511;

int n;

int f[maxn][maxn];

int main()
{
    cin >> n;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=i; j++)
            cin >> f[i][j];

    for(int i=n-1; i>=1; i--)
        for(int j=1; j<=n; j++)
            f[i][j]+=max(f[i+1][j],f[i+1][j+1]);

    cout << f[1][1];

    return 0;
}

最长上升子序列

在这里插入图片描述


#include<iostream>
using namespace std;

const int maxn=1111;

int a[maxn],f[maxn];          //a表示给出的数组,f表示到达每个点的最长子序列

int main()
{
    int n;
    cin >> n;
    for(int i=1; i<=n; i++)
        cin >> a[i];
    for(int i=1; i<=n; i++)
    {
        f[i]=1;                     //先对每个点的长度初始化为1
        for(int j=1; j<i; j++)      //从头开始遍历,一直到i之前
            if(a[j]<a[i])               //如果a[j]<a[i],则可以将第i个数放到序列中,则f[i]为f[j]+1
                f[i]=max(f[i],f[j]+1);
    }

    int res=-n;

    for(int i=1; i<=n; i++)          //最后找出所有点的最大长度即可
        res=max(res,f[i]);

    cout << res;

    return 0;
}

最长上升子序列2

思路分析

这道题本身是将数据范围给大了,所以要考虑简便算法

1.对于 3 1 2 1 8 5 6 对于8而言,可以插入到3后面,也可以插入到1后面,但是我们选择插入到1的后面,因为范围更大,所以插入到3的后面就多余了,对于朴素算法而言,我们需要将8插入到前面的所有比他小的值后面,然后找出最大长度
2.注意q[i] 表示长度是i的上升子序列最后一个数的最小值,q[i]是随着i的值递增的
3.对于a[i]而言,我们肯定是要将a[i]放到前面的一个最大长度,并且该最大长度的最后一个值要小于a[i],要找长度i,所以找q[i],所以我们采用2分的思路,在q[]中找出小于a[i]的最大值,此时找到的这个位置比如q[4],表示长度为4是之前的最大长度,并且长度为4的上升子序列中的最小值为q[4],并且q[4]是小于a[i]的最大值,也就是q[5]的值>=a[i],表示长度为5的上升子序列的最后一个值的最小值是>=a[i],由于q的属性,则q[5]=a[i]


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int a[N];
int q[N];               //q存放的是,每种长度的上升子序列的结尾的值最小是多少,其实这里随着长度的增加,结尾的值也在递增,呈一个上升趋势

int main()
{
    scanf("%d", &n);        //输入n

    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);       //输入a[i]

    int len = 0;
    for (int i = 0; i < n; i ++ )
    {             //数字a[i]可以接到任何一个比自己小的值的后面,如果想要长度最大,则需要将a[i]接到比a[i]小的最大值的后面
        int l = 0, r = len;
        while (l < r)           //通过2分,找出小于a[i]的最大的数,这个二分也是在q中去寻找一个合适的长度,
        {
            int mid = l + r +1 >> 1;
            if (q[mid] < a[i]) l = mid;   //中间这个值q[mid]小于a[i],现在要找到小于a[i]的最大值,所以从该值开始一直往后找,则让l=mid,正常的思路如果是要找后半部分,则需要l=mid+1,但是我们现在是l=mid,则mid=l+r+1>>1
            else r = mid-1;
        }
        len = max(len, r + 1);
        q[r + 1] = a[i];
    }


    printf("%d\n", len);

    return 0;
}



最长公共子序列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述



#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N];

int main()
{
    scanf("%d%d", &n, &m);           //输入n,m
    scanf("%s%s", a + 1, b + 1);         //a,b均为字符串数组,其中加1表示从下标为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]);
            if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }

    printf("%d\n", f[n][m]);

    return 0;
}


最短编辑距离

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

石子合并

在这里插入图片描述
在这里插入图片描述

整数划分

在这里插入图片描述

该问题可以看成容量为n的一个背包,一共有n个体积为1,2,3,4…n的物品,相当于是问将背包装满一共有多少种装法,并且每个物品可以有n中选择
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
体积升序排列
在这里插入图片描述


#include<iostream>
using namespace std;

const int maxn=1010;

const int mod=1e9+7;

int n;

int f[maxn];

int main()
{
    cin >> n;

    f[0]=1;

    for(int i=1; i<=n; i++)                  //相当于是从1到i,
        for(int j=i; j<=n; j++)                //此时体积也是从i到n
            f[j]=(f[j]+f[j-i])%mod;

    cout << f[n];

    return 0;

}

计数问题

在这里插入图片描述

计数问题

在这里插入图片描述
在这里插入图片描述


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

const int maxn=101010;

int get(vector<int> num,int l,int r)
{
    int res=0;
    for(int i=l; i>=r; i--)
        res=res*10+num[i];
    return res;
}

int power10(int x)
{
    int res=1;
    for(int i=0; i<x; i++)
        res*=10;
    return res;
}

int count(int n, int x)             //在n中,统计x出现的个数
{
    if (!n) return 0;           //如果n为0,直接返回0

    vector<int> num;                 //num存放n中每一位的个数

    while (n)             //其实这个num是逆着存放n的位数
    {
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();

    int res = 0;                                                    //res表示每一位中出现的个数
    for (int i = n - 1 - !x; i >= 0; i -- )                //i应该是从倒数第一位开始的,如果要统计的数x为0,则从倒数第2位开始,i>=0
    {
        if (i < n - 1)          //如果i从倒数第2位开始,也就是正数第1位
        {
            res += get(num, n - 1, i + 1) * power10(i);     //
            if (!x) res -= power10(i);          //如果要统计的数为0,则少掉了10^i个
        }

        if (num[i] == x) res += get(num, i - 1, 0) + 1;         //如果当前位置上的这个数等于x,则后面一共有000~efg,共efg+1个
        else if (num[i] > x) res += power10(i);         //如果当前位置的数>x,则值为000--999,共10^i个
    }

    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)           //输入a,b,其中a为0,则循环结束
    {
        if (a > b) swap(a, b);          //如果a>b,则交换,保证第1个数小,第2个数大

        for (int i = 0; i <= 9; i ++ )
            //注意是a-1
            cout << count(b, i) - count(a - 1, i) << ' ';             //统计a--b之间,0~9中所有数的个数,就是统计b中0~9所有数的个数再减去a中0~9中所有数的个数
        cout << endl;           //换行
    }

    return 0;
}

蒙德里安的梦想

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最短Hamilton路径

在这里插入图片描述


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

using namespace std;

const int N = 20, M = 1 << N;     //注意N,M的设置

int n;
int w[N][N];      //这就是端点值
int f[M][N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            cin >> w[i][j];         //输入端点值

    memset(f, 0x3f, sizeof f);      //因为要找最小值,所以对f进行初始化为最大
    f[1][0] = 0;        //注意这里f[1][0]设为0,注意这个初始值

    for (int i = 0; i < 1 << n; i ++ )          //i就是   邹精
        for (int j = 0; j < n; j ++ )       //j就是0~n
            if (i >> j & 1)         //i要把j收拾下哈
                for (int k = 0; k < n; k ++ )       //k也是从0--n
                    if (i >> k & 1)         //i把k也收拾一哈
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);       //注意加括号

    cout << f[(1 << n) - 1][n - 1];

    return 0;
}


没有上司的舞会

在这里插入图片描述


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

using namespace std;

const int N = 6010;

int n;
int h[N], e[N], ne[N], idx;
int happy[N];
int f[N][2];
bool has_fa[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)
{
    f[u][1] = happy[u];         //对于选择节点u来说,其幸福指数为happy[u]

    for (int i = h[u]; i!=-1; i = ne[i])
    {
        int j = e[i];
        dfs(j);

        f[u][1] += f[j][0];         //u是j的父亲节点,选择u就不能选择j
        f[u][0] += max(f[j][0], f[j][1]);       //不选择u,则选择其孩子节点的最值
    }
}

int main()
{
    scanf("%d", &n);        //输入n个节点,输入每个人的幸福指数

    for (int i = 1; i <= n; i ++ ) scanf("%d", &happy[i]);

    memset(h, -1, sizeof h);                //对h初始化

    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);     //输入a,b节点

        add(b, a);          //b是a的父亲节点
        has_fa[a] = true;           //标记a有父亲节点
    }

    int root = 1;
    while (has_fa[root]) root ++ ;         //找到根节点

    dfs(root);      //深搜

    printf("%d\n", max(f[root][0], f[root][1]));

    return 0;
}


滑雪

在这里插入图片描述


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

using namespace std;

const int N = 310;

int n, m;
int g[N][N];
int f[N][N];

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};   //左上右下

int dp(int x, int y)
{
    int &v = f[x][y];       //通过&v,后面对v的值的修改直接作用于f[x][y]
    if (v != -1) return v;              //如果v!=-1,表示之前已经访问过了

    v = 1;      //v的最小值为1,因为最起码从该点出发还能有一个点
    for (int i = 0; i < 4; i ++ )       //一共遍历4个方向
    {
        int a = x + dx[i], b = y + dy[i];
        if (a >= 1 && a <= n && b >= 1 && b <= m && g[x][y] > g[a][b])      //必须在范围内,且该点的高度大于下个点
            v = max(v, dp(a, b) + 1);       //通过递归的方式
    }

    return v;
}

int main()
{
    scanf("%d%d", &n, &m);          //输入n行,m列
    
    for (int i = 1; i <= n; i ++ )          //一共是n行,m列
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &g[i][j]);      //g表示每个点的高度

    memset(f, -1, sizeof f);        //对f初始化,全部为-1

    int res = 0;            //res表示所经过的区域
    
    for (int i = 1; i <= n; i ++ )          //共n个点,
        for (int j = 1; j <= m; j ++ )
            res = max(res, dp(i, j));           //找出最大的通过的区域数

    printf("%d\n", res);        //输出

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值