动态规划问题

01背包问题


特点: 每件物品最多用一次,可以不用

N件物品,容量为V的背包。每件物品只能用一次。

朴素做法:

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N], w[N];
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 = 1; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
        }
    }

    cout << f[n][m] << endl;
}

优化后的代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N];
int v[N], w[N];
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 = m; j >= v[i]; j--) {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }

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

完全背包问题

每件物品有无限多个

朴素做法

//会超时
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
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 = 0; j <= m;j++)
            for(int k=0;v[i]*k<=j;k++)
            f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
    cout<<f[n][m]<<endl;
    
    
    return 0;
}	

优化算法(对比0-1背包的朴素做法)

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
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 = 0; j <= m;j++)
        {
            f[i][j] = f[i-1][j];
            if(j>=v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
        }
            
    cout<<f[n][m]<<endl;
    
    
    return 0;
}

对比:

0-1背包朴素   f[i][j] = max(f[i-1][j], f[i - 1][j - v[i]] + w[i]);//上一层:从大到小遍历
完全背包双循环 f[i][j] = max(f[i-1][j], f[i][j-v[i]]+w[i]);//本层:从小到大遍历

继续优化成一维

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
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++)
        {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
            
    cout<<f[m]<<endl;
    
    
    return 0;
}

思考:为什么0-1背包是从大到小遍历体积,而完全背包是从小到大遍历体积?

从m往前不会重复,从前往后可以重复多个物品

多重背包问题

每件物品有s[i]件

朴素版本

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N][N];
int v[N],w[N],s[N];
int n,m;
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] && k*v[i]<=j;k++)
                f[i][j] = max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
            
    cout<<f[n][m]<<endl;
    
    
    return 0;
}

二进制优化为0-1背包求解

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 22010;
int f[N];
int v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    int a,b,s,cnt;
    //打包
    for(int i=1;i<=n;i++) 
    {
        cin>>a>>b>>s;
        int k=1;
        while(k<=s)
        {
            cnt++;
            v[cnt] = k*a;
            w[cnt] = k*b;
            s -= k;
            k *= 2;
        }
        if(s<k)
        {
            cnt++;
            v[cnt] = a*s;
            w[cnt] = b*s;
        }
    }
    
    n = cnt;//按包更新物品总数
    //0-1背包问题
    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]<<endl;
    
    return 0;
}

分组背包问题

把物品分成N组,每组有若干个,每组中最多只能选择一个
#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 ++ )
    {
        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 -- )
            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;

    return 0;
}

线性dp

  1. 数字三角形
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510;
int n;
int a[N][N],f[N][N];
int INF = 1e9;

int main()
{
    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<=i+1;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(res,f[n][i]);
    
    printf("%d",res);
    
    
    return 0;
}

AcWing 895. 最长上升子序列

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n;
int a[N], f[N];

int main()
{
    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 ++ )
            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\n", res);

    return 0;
}

AcWing 896. 最长上升子序列 II(偏贪心)

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int a[N];//存储输入
int q[N];//存储每个长度结尾的最小值

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

    int len = 0;
    for (int i = 0; i < n; i ++ )
    {
        int l = 0, r = len;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (q[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);//接a[i]后长度加1
        q[r + 1] = a[i];
    }

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

    return 0;
}

AcWing 897. 最长公共子序列

image-20220708103437545

00 包含在01和10中
#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);
    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]);
            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;
}

AcWing 902. 最短编辑距离

#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%s", &n, a + 1);//下标从1开始 因为有i-1 
    scanf("%d%s", &m, b + 1);

    for (int i = 0; i <= m; i ++ ) f[0][i] = i;
    for (int i = 0; i <= n; i ++ ) f[i][0] = i;

    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]);
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }

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

    return 0;
}

AcWing 899. 编辑距离(应用上一题目)

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

using namespace std;

const int N = 15, M = 1010; 

int n, m;
int f[N][N];   
char str[M][N];

int edit_distance(char a[], char b[])
{ 
    int la = strlen(a + 1), lb = strlen(b + 1);

    for (int i = 0; i <= lb; i ++ ) f[0][i] = i;
    for (int i = 0; i <= la; i ++ ) f[i][0] = i;

    for (int i = 1; i <= la; i ++ )
        for (int j = 1; j <= lb; j ++ )
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j]));
        }

    return f[la][lb];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ ) scanf("%s", str[i] + 1);

    while (m -- )
    {
        char s[N];
        int limit;
        scanf("%s%d", s + 1, &limit);

        int res = 0;
        for (int i = 0; i < n; i ++ )
            if (edit_distance(str[i], s) <= limit)
                res ++ ;

        printf("%d\n", res);
    }

    return 0;
}

区间dp

AcWing 282. 石子合并

image-20220708111517145

用到前缀和

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQlRGXxf-1658110437969)(…/…/AppData/Roaming/Typora/typora-user-images/image-20220708111309646.png)]

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 310;

int n;
int s[N];//前缀和
int f[N][N];

int main()
{
    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 - 1];

    for (int len = 2; len <= n; len ++ )//长度
        for (int i = 1; i + len - 1 <= n; i ++ )//起点
        {
            int l = i, r = i + len - 1;
            f[l][r] = 1e8;
            for (int k = l; k < r; k ++ )
                f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
        }

    printf("%d\n", f[1][n]);
    return 0;
}

核心代码

    for (int len = 2; len <= n; len ++ )//枚举长度
        for (int i = 1; i + len - 1 <= n; i ++ )//枚举起点
        {
            int l = i, r = i + len - 1;
            f[l][r] = 1e8;
            for (int k = l; k < r; k ++ )
                f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);

计数类dp

AcWing 900. 整数划分

image-20220716103453269
看成背包体积为n ,物品体积1~n,物品无限多个,恰好装满背包的方案数
完全背包问题  

完全背包解法

状态表示:
f[i][j]表示只从1~i中选,且总和等于j的方案数
状态转移方程:
f[i][j] = f[i - 1][j] + f[i][j - i];
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N];

int main()
{
    cin >> n;

    f[0] = 1;
    for (int i = 1; i <= n; i ++ )
        for (int j = i; j <= n; j ++ )
            f[j] = (f[j] + f[j - i]) % mod;

    cout << f[n] << endl;

    return 0;
}

其他解法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jEWmAFzj-1658110437970)(…/…/AppData/Roaming/Typora/typora-user-images/image-20220716160953719.png)]

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N][N];

int main()
{
    cin >> n;

    f[1][1] = 1;
    for (int i = 2; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;

    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = (res + f[n][i]) % mod;

    cout << res << endl;

    return 0;
}

数位统计DP(翻车呜呜呜)

分情况讨论

AcWing 338. 计数问题

前缀和思想 转化

image-20220716163353091 image-20220718091711088
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 10;

/*

001~abc-1, 999

abc
    1. num[i] < x, 0
    2. num[i] == x, 0~efg
    3. num[i] > x, 0~999

*/

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;
    while (x -- ) res *= 10;
    return res;
}

int count(int n, int x)
{
    if (!n) return 0;

    vector<int> num;
    while (n)
    {
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();

    int res = 0;
    for (int i = n - 1 - !x; i >= 0; i -- )
    {
        if (i < n - 1)
        {
            res += get(num, n - 1, i + 1) * power10(i);
            if (!x) res -= power10(i);
        }

        if (num[i] == x) res += get(num, i - 1, 0) + 1;
        else if (num[i] > x) res += power10(i);
    }

    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);

        for (int i = 0; i <= 9; i ++ )
            cout << count(b, i) - count(a - 1, i) << ' ';
        cout << endl;
    }

    return 0;
}

状态压缩DP

朴素算法

#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 (cin >> n >> m, n || m)
    {
        for (int i = 0; i < 1 << n; i ++ )
        {
            int cnt = 0;
            st[i] = true;
            for (int j = 0; j < n; j ++ )
                if (i >> j & 1)
                {
                    if (cnt & 1) st[i] = false;
                    cnt = 0;
                }
                else cnt ++ ;
            if (cnt & 1) st[i] = false;
        }

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

t main()
{
while (cin >> n >> m, n || m)
{
for (int i = 0; i < 1 << n; i ++ )
{
int cnt = 0;
st[i] = true;
for (int j = 0; j < n; j ++ )
if (i >> j & 1)
{
if (cnt & 1) st[i] = false;
cnt = 0;
}
else cnt ++ ;
if (cnt & 1) st[i] = false;
}

    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;

}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工具人来了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值