Skip to content

Latest commit

 

History

History
257 lines (211 loc) · 6.17 KB

DP.md

File metadata and controls

257 lines (211 loc) · 6.17 KB

背包DP

DP从两个方面考虑:

状态表示:

用几维去表示状态,这里的状态包括集合以及属性

集合:所有的选法,以及需要满足的条件

属性:min、max、count

状态计算:

状态转移方程:集合的划分,该状态怎么从之前状态推出来

优化:对代码、方程进行等价变形

Snipaste_2019-09-13_17-06-58.png

01背包DP

ACW 01背包

n个物品,背包体积V,每个物品两个属性,v-体积,w-权重,每件物品只能装一次。

目标:选出来的物品都总价值最大

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

优化:一维储存

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int dp[N],n,m;
int v[N],w[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>w[i];
    }
  	//同样更新n轮,每次只保存j的信息
    for(int i=1;i<=n;i++){
      //逆序循环j,如果正序,会污染dp[i-1][j-v[i]]的值,因为我们想要的是第i-1轮,但是当我们更新第i轮的时候,由于j-v[i]小于j,所以我们会先更新j-v[i],导致它变成了dp[i][j-v[i]],所以要倒序更新
        for(int j=m;j>=v[i];j--){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m];
    return 0;
}

完全背包DP

ACW 完全背包

每件物品可无限次装入。

//朴素好理解但TLE版本
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int dp[N][N],n,m;
int w[N],v[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++){
          //不再将状态划分为两份,而是划分为k+1份,其中k=0包括了上面的dp[i-1][j],上面是求两份的最大值,现在求k+1份最大值
                for(int k=0;k*v[i]<=j;k++){
                    dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
                }
            }
    }
    cout<<dp[n][m];
    return 0;
}
//优化1.0版本--找规律
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w....)
dp[i][j-v]=max(dp[i-1][j-v],dp[i-1][j-2*v]+2*w)
上面比下面多了一个w,所以dp[i][j]=max(dp[i-1][j],dp[i][j-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]>=0)
        f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
//优化2.0版本--一维
for(int i = 1 ; i <=n ;i++)
  //与01背包只有循环的顺序不一样
  for(int j = v[i] ; j <=m ;j++)
{
        f[j]=max(f[j],f[j-v[i]]+w[i]);
}

多重背包问题

每个物品的数量不一致,各有si件。

//暴力写法,与完全背包一样,唯一的限制是k<=s[i];
数据范围
0<N,V≤100
0<vi,wi,si≤100

for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
            for(int k=0;k*v[i]<=j&&k<=s[i];k++)
                dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
            }
    }
cout<<dp[n][m];
/*优化1.0--二进制优化
多重背包转移01背包问题
将多重背包进行分组,假设s=1000
可以用1,2,4,8,...,2^k C 可以完全表示s(C是常数)
首先可以凑出来0-2^k的所有值,之后加上C,可以凑出任意s*/
这样做的目的是,将s拆分为logs,1个多重背包问题就变成了logs个01背包问题,总的时间复杂度是n*v*logs,比之前的n*v*logs低

N*logs个01背包问题,开数组注意
#include<bits/stdc++.h>
using namespace std;
const int N=12010;
int f[N],n,m;
struct Good{
    int v;
    int w;
};
int main(){
    cin>>n>>m;
    vector<Good> goods;
    for(int i=0;i<n;i++){
        int v,w,s;
        cin>>v>>w>>s;
        for(int k=1;k<=s;k*=2){
            s-=k;
            goods.push_back({k*v,k*w});
        }
        if(s>0) goods.push_back({s*v,s*w});
    }
    for(auto g:goods){
        for(int j=m;j>=g.v;j--){
            f[j]=max(f[j],f[j-g.v]+g.w);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

分组背包问题

物品有n组,每组里面有若干个,每组里只能选一个。

//与01背包问题类似
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int dp[N];
int n,m;
int v[N][N],w[N][N],s[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int k=1;k<=s[i];k++){
            cin>>v[i][k]>>w[i][k];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=0;j--){
            for(int k=1;k<=s[i];k++){
                if(v[i][k]<=j) dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
            }
        }
    }
    cout<<dp[m];
    return 0;
}

线性DP

数字三角形

最长上升子序列

最长公共子序列

最小硬币数

//给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
思路:dp[x]代表金额x所需的最小硬币数,dp[0]=0,x=1时,dp[1]=dp[0]+1
   	 x=2时,dp[2]=dp[1]+1....dp[i]=min(dp[i], dp[i-coins[j]]+1)
  	 金额i最小是由coins里的每个金额加上前面的合适金额构成的
  int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,0x3f3f3f3f);
        dp[0]=0;
        for(int i=1;i<=amount;i++){
            for(int j=0;j<coins.size();j++){
                if(coins[j]<=i)  dp[i] = min(dp[i], dp[i-coins[j]]+1);
            }
        }
        return dp[amount]==0x3f3f3f3f? -1 :dp[amount];
    }