ACWing 第五章 动态规划

背包问题

N N N个物品,背包体积 V V V, 不一定要装满背包

01背包:每件物品最多用一次
完全背包:每件物品有无限个
多重背包:每个物品有 S i S_i Si个,朴素版+优化版
分组背包 N N N组,每组有若干个,水果组,蔬菜组,每组最多选择一物品,每组互斥

在这里插入图片描述
子集划分:不重复,不漏

动 态 规 划 = 状 态 表 示 f ( i , j ) + 状 态 计 算 动态规划=状态表示f(i,j)+状态计算 =f(i,j)+
状 态 表 示 f ( i , j ) = 状态表示f(i,j)= f(i,j)=
集 合 ( 所 有 选 法 , 条 件 ( 只 考 虑 前 i 个 物 品 , 总 体 积 < = j ) ) + 属 性 ( M a x , M i n , 数 量 ) 集合(所有选法,条件(只考虑前i个物品,总体积<=j))+属性(Max,Min,数量) (,(i,<=j))+(Max,Min,)

在这里插入图片描述 f ( i , j ) = m a x [ f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ] f(i,j)=max[f(i-1,j),f(i-1,j-vi)+wi] f(i,j)=max[f(i1,j),f(i1,jvi)+wi]

AcWing 2. 01背包问题

AcWing 2. 01背包问题

#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
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;
    
    return 0;
}

f [ i ] [ j ] f[i][j] f[i][j]只用到前面一行的数据,二维可以用一维来存储

    for(int i = 1; i <= n; i++){
        for(int j = v[i]; j <= m; j++){  //直接从v[i]开始
            f[j] = max(f[j],f[j-v[i]] + w[i]); //使用的是当前层更新的数据f[j-v[i]]]数据覆盖了
        }
    }
    for(int i = 1; i <= n; i++)
        for(int j = m; j >= v[i]; j--)  //直接从v[i]开始
            f[j] = max(f[j],f[j-v[i]] + w[i]); //使用的是当前层更新的数据f[j-v[i]]]数据覆盖了

AcWing 3. 完全背包问题

AcWing 3. 完全背包问题
在这里插入图片描述
超时写法
k == 0时包括了 f[i-1][j] 的选法,所以不需要向上面一样赋值f[i-1][j]

#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int f[N][N];
int n,m;
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    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; 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;
}

f [ i , j ] = m a x ( f [ i − 1 , j ] , f [ i − 1 , j − v ] + w , f [ i − 1 , j − 2 v ] + 2 w , . . . . f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,.... f[i,j]=max(f[i1,j],f[i1,jv]+w,f[i1,j2v]+2w,....
f [ i , j − v ] = m a x ( − − − f [ i − 1 , j − v ] , − − f [ i − 1 , j − 2 v ] + w , f [ i − 1 ] [ j − 3 v ] + 2 w f[i,j-v]=max( --- f[i-1,j-v],--f[i-1, j-2v]+w,f[i-1][j-3v]+2w f[i,jv]=max(f[i1,jv],f[i1,j2v]+w,f[i1][j3v]+2w
f [ i , j ] f[i,j] f[i,j]和同一行的 f [ i , j − v ] f[i,j-v] f[i,jv]非常相似
在这里插入图片描述

在这里插入图片描述

#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int f[N][N];
int n,m;
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    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;
}

优化,二维变一维

#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int f[N];
int n,m;
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    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++){
            if(j >= v[i]) f[j] = max(f[j],f[j-v[i]] + w[i]); //这里就没有01背包,覆盖的问题了
        }
    }
    cout << f[m] << endl;
    return 0;
}

01背包代码和完全背包代码看似只有第二次遍历顺序不一样,但是含义本质差别很大

AcWing 4. 多重背包问题

AcWing 4. 多重背包问题
在这里插入图片描述

暴力写法
本题目n = 100, n 3 = 1 e 6 n^3=1e^6 n3=1e6 没超时

#include <iostream>
using namespace std;
const int N = 110;
int v[N],w[N],s[N];
int n,m;
int f[N][N];
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    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++){ // k = 0包含了 f[i-1][j]的情况
                 f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + w[i]*k);
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

AcWing 5. 多重背包问题 II

AcWing 5. 多重背包问题 II

上一题数据量增大,必须优化!
在这里插入图片描述

线性dp

AcWing 898. 数字三角形

AcWing 898. 数字三角形

在这里插入图片描述

#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
int a[N][N];
int f[N][N];
int n;



int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    memset(f,0x80,sizeof f); //有负数,就不能初始化为0了
    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 ans = 0x80000000;
    for(int i = 1; i <= n; i++) ans = max(ans,f[n][i]);
    cout << ans << endl;
    
    
   
    return 0;
}

AcWing 895. 最长上升子序列

AcWing 895. 最长上升子序列

在这里插入图片描述
O ( n 2 ) O(n^2) O(n2)算法

#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int a[N];
int n;

int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++){
        f[i] = max(f[i],1); //a[i]只有一个数
        for(int j = i+1; j <= n; j++){ // 用当前的最大上升长度,更新后面的最大上升子序列
            if(a[j] > a[i]) f[j] = max(f[j],f[i]+1);
        }
    }
    int ans = 0x80000000;
    for(int i = 1; i <= n; i++) ans = max(ans,f[i]);
    cout << ans << endl;

    
    return 0;
}
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int a[N];
int n;

int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    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-1; j++){ //用前面的去更新当前i的最长上升子序列
            if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
        }
    }
    int ans = 0x80000000;
    for(int i = 1; i <= n; i++) ans = max(ans,f[i]);
    cout << ans << endl;

    
    return 0;
}

AcWing 896. 最长上升子序列 II

AcWing 896. 最长上升子序列 II在这里插入图片描述
所有不同长度的最长上升子序列最小结尾的值存下来

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

i f q 4 < a i 产 生 了 长 度 为 5 的 最 长 上 升 子 序 列 , 更 新 一 下 q 5 if q_4<a_i产生了长度为5的最长上升子序列,更新一下q_5 ifq4<ai5q5
这种做法更像贪心

n l o g ( n ) nlog(n) nlog(n)

#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int q[N]; //不同长度的最长上升子序列最小结尾
int a[N];
int n;

int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    memset(q,0x7f,sizeof q);
    int ans = 0;
    for(int i = 1; i <= n; i++){
        int t = a[i]; 
        //二分 t
        int l = 1, r = i-1;
        while(l <= r){ // lower_bound
            int mid = l + (r-l)/2;
            if(l == r && q[mid] >= t) break; 
            if(q[mid] < t) l = mid + 1; //q[mid] = t 时,覆盖最小值
            else r = mid;
        }
        q[l] = min(q[l],t); //如果当前值相同,覆盖当前值,如果不同,那么计算长度+1的最长上升子序列的最小结尾
        ans = max(ans,l); //找最大的长度下标
    }
    cout << ans << endl;
    return 0;
}

简化写法

#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int q[N]; //不同长度的最长上升子序列最小结尾
int a[N];
int n;

int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    int ans = 0;
    for(int i = 1; i <= n; i++){
        int t = a[i]; 
        //二分 t
        int l = 1, r = ans; // r = 找到的最大长度,保证数组是有序的
        while(l <= r){ // lower_bound
            int mid = l + (r-l)/2;
            if(l == r && q[mid] >= t) break; 
            if(q[mid] < t) l = mid + 1; //q[mid] = t 时,覆盖最小值
            else r = mid;
        }
        q[l] = t; // t一定是最小的 
        ans = max(ans,l); 
    }
    cout << ans << endl;
    return 0;
}

AcWing 897. 最长公共子序列

AcWing 897. 最长公共子序列
集合:所有在第一个序列的前i个字母出现,且在第二个序列的前j个字母中出现的子序列
属性:长度的最大值
状态计算:以 a i , b j a_i,b_j ai,bj 是否包含在子序列之中,有四种情况
在这里插入图片描述
在这里插入图片描述

f [ i − 1 , j ] 包 含 01 这 种 情 况 f[i-1,j]包含01这种情况 f[i1,j]01
f [ i − 1 , j ] 子 序 列 包 含 在 f [ i , j ] 之 中 f[i-1,j]子序列包含在f[i,j]之中 f[i1,j]f[i,j]
f [ i , j − 1 ] 包 含 10 这 种 情 况 f[i,j-1]包含10这种情况 f[i,j1]10
f [ i , j − 1 ] 子 序 列 包 含 在 f [ i , j ] 之 中 f[i,j-1]子序列包含在f[i,j]之中 f[i,j1]f[i,j]

#include <iostream>
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    cin >> 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);
        }
    }
    cout << f[n][m] << endl;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值