22.动态规划

dp的俩个性质

无后效性

最优子结构

dp的俩大要素

状态

状态转移方程

基础DP

支付问题

#include <iostream>
#include <climits>

using namespace std;
//dp支付问题
/*
 * 15
 * 1 5 11
 * 按贪心算法来做的话,11*1+1*4-->5个
 * 正常来算的话,5*3-->3个
 * 最终答案应该为3,因为贪心具有后效性
 * 用dp来做
 * */
const int N=1e4+10;
int dp[N];
//状态:dp[i]-->支付i元所需最小的硬币数量
//状态转移方程:dp[i]=min{dp[i-11],dp[i-4],dp[i-1]}+1
int main(){
    int w;cin>>w;
    for(int i=1;i<=w;i++){
        int mi=INT_MAX;
        if(i>=1) mi=min(mi,dp[i-1]);
        if(i>=5) mi=min(mi,dp[i-5]);
        if(i>=11) mi=min(mi,dp[i-11]);
        dp[i]=mi+1;
        //print dp table
        cout<<"dp["<<i<<"]="<<dp[i]<<endl;
    }
    cout<<dp[w]<<endl;
    return 0;
}

二维格子

#include <iostream>
#include <vector>
#include <climits>
using namespace std;
const int N=1e3+10;
int a[N][N],dp[N][N];
//状态:dp[i][j]表示从起点(1,1)到终点(i,j)所消耗的最小体力
//状态转移方程:dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j]

int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>a[i][j];
    //边界处理:
    //第一行
    for(int j=1;j<=n;j++) dp[1][j]=dp[1][j-1]+a[1][j];
    //第一列
    for(int i=1;i<=n;i++) dp[i][1]=dp[i-1][1]+a[i][1];
    for(int i=2;i<=n;i++){
        for(int j=2;j<=n;j++){
            dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j];
        }
    }
    //print_dp_Table
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cout<<dp[i][j]<<"\t";
        }cout<<endl;
    }
    cout<<dp[n][n]<<endl;
    return 0;
}

三角形最佳路径问题

#include <iostream>
#include <vector>
#include <climits>
using namespace std;
const int N=1e3+10;
int a[N][N],dp[N][N];
//状态:dp[i][j]-->(1,1)点到(i,j)点的最大路径和
//状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]

int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            cin>>a[i][j];
        }
    }
//    for(int i=1;i<=n;i++){
//        for(int j=1;j<=i;j++){
//            cout<<a[i][j]<<"  ";
//        }cout<<endl;
//    }
    //第一列和对角线:
    dp[1][1]=a[1][1];
    for(int i=2;i<=n;i++){
        dp[i][1]=dp[i-1][1]+a[i][1];
        dp[i][i]=dp[i-1][i-1]+a[i][i];
    }
    for(int i=3;i<=n;i++){
        for(int j=2;j<i;j++){
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
        }
    }
//        for(int i=1;i<=n;i++){
//        for(int j=1;j<=i;j++){
//            cout<<dp[i][j]<<"  ";
//        }cout<<endl;
//    }
    int mx=INT_MIN;
    for(int j=1;j<=n;j++){
        mx=max(mx,dp[n][j]);
    }
    cout<<mx<<endl;
    return 0;
}

单序列DP

最大子段和(连续子段)

#include <iostream>
#include <climits>
using namespace std;
const int N=2e5+10;
int a[N],dp[N];

//状态:dp[i]-->以第i个元素为结尾的最大子段和
//状态转移方程:dp[i]=max(dp[i-1]+a[i],a[i])

int main(){
    int n,mx=INT_MIN;cin>>n;
	//-1 2 3 -4 1 3
    //-1 2 5  
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        //第i个状态由第i-1个状态加一些决策得出
        dp[i]=max(dp[i-1]+a[i],a[i]);
        mx=max(mx,dp[i]);
    }
    cout<<mx<<endl;
    return 0;
}

压维的序列DP(最大子矩阵)

#include <iostream>
#include <climits>
using namespace std;
const int N=1e2+10;
int a[N][N],dp[N],b[N],presum[N][N];
//状态:dp[j]-->压维后以j结尾元素的最大字段和也就是最大子矩阵和
//状态转移方程:dp[j]=max(dp[j-1]+a[j],a[j])

int main(){
    //处理前缀和
    int n;cin>>n;
    for(int i=1;i<=n;i++) {
        for (int j = 1; j <= n; j++) {
            cin >> a[i][j];
            //固定j列,第j列的前缀和
            presum[i][j] = presum[i - 1][j] + a[i][j];
        }
    }
    int mx=INT_MIN;
    for(int i1=1;i1<=n;i1++){
        for(int i2=i1;i2<=n;i2++){//枚举i1 i2
            //压维
            for(int j=1;j<=n;j++){
                b[j]=presum[i2][j]-presum[i1-1][j];
            }
            for(int j=1;j<=n;j++){
                dp[j]=max(dp[j-1]+b[j],b[j]);
                mx=max(mx,dp[j]);
            }
        }
    }
    cout<<mx<<endl;
    return 0;
}

最长上升子序列(LIS)

Longest Increase Subsequence
#include <iostream>
#include <climits>

using namespace std;
const int N=1e3+10;
int a[N],pre[N],n,LIS[N];
//dp_LIS[i]-->以i结尾的最长上升子序列长度
int main(){
    /*    1  2  3  4  5  6  7
     * a  1  7  3  5  9  4  8
     * dp 1  2  2  3  4  3  4
     * */
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        LIS[i]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i-1;j++){
            if(a[j]<a[i]) LIS[i]=max(LIS[i],LIS[j]+1);
        }
    }
    int mx=INT_MIN;
    for(int i=1;i<=n;i++){
        mx=max(mx,LIS[i]);
    }
    cout<<mx<<endl;
    return 0;
}

求最长不下降序列

#include <iostream>
#include <climits>

using namespace std;
const int N=1e3+10;
int a[N],pre[N],n,LIS[N];
//dp_LIS[i]-->以i结尾的最长上升子序列长度
void print(int s){
    if(s==0) return;
    print(pre[s]);
    cout<<a[s]<<" ";
}
int main(){
    /*    1  2  3  4  5  6  7
     * a  1  7  3  5  9  4  8
     * dp 1  2  2  3  4  3  4
     * 1 3 6 7
     * 1 3 4 8
     * */
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        LIS[i]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i-1;j++){
            if(a[j]<=a[i]&&LIS[i]<LIS[j]+1){
                LIS[i]=LIS[j]+1;
                pre[i]=j;
            }
        }
    }
    int mx=INT_MIN,mxId=0;
    for(int i=1;i<=n;i++){
        if(mx<LIS[i]){
            mx=LIS[i];
            mxId=i;
        }
    }
    cout<<"max="<<mx<<endl;
    print(mxId);
    return 0;
}

合唱队形

#include <iostream>
#include <climits>

using namespace std;
const int N=1e2+10;
int a[N],dp_up[N],dp_down[N];

int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        dp_up[i]=dp_down[i]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            if(a[j]<a[i]){
                dp_up[i]=max(dp_up[j]+1,dp_up[i]);
            }
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=i+1;j<=n;j++){
            if(a[j]<a[i]){
                dp_down[i]=max(dp_down[j]+1,dp_down[i]);
            }
        }
    }
    int mx=INT_MIN;
    for(int i=1;i<=n;i++){
        mx=max(mx,dp_down[i]+dp_up[i]-1);
    }
    cout<<n-mx<<endl;
    return 0;
}

怪盗基德的滑翔翼

#include <iostream>
#include <climits>
#include <cstring>
using namespace std;
const int N=1e3+10;
int a[N],dp_down1[N],dp_down2[N],ans=0;
//dp[i]-->以第i元素结尾的最长下降子序列长度
//dp[i]=max(dp[i],dp[j]+1);
void solve(){
    int n;cin>>n;
    ans=0;
    memset(dp_down1,0,sizeof dp_down1);
    memset(dp_down2,0,sizeof dp_down2);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        dp_down1[i]=dp_down2[i]=1;
    }
    //以i为结尾的下降
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i-1;j++){
            if(a[j]<a[i]){
                dp_down1[i]=max(dp_down1[i],dp_down1[j]+1);
            }
        }
    }
    //以i为结尾的下降
    for(int i=n;i>=1;i--){
        for(int j=i+1;j<=n;j++){
            if(a[j]<a[i]){
                dp_down2[i]=max(dp_down2[i],dp_down2[j]+1);
            }
        }
    }
    for(int i=1;i<=n;i++){
        ans=max(ans,max(dp_down1[i],dp_down2[i]));
    }
    cout<<ans<<endl;
}
int main(){
    int t;cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

多序列DP

最长公共子序列(LCS)

Longest Common Subsequence
#include <iostream>

using namespace std;

/*
 * dp[i][j]-->以s1的前i个字符和s2的前j个字符产生的最长公共子序列长度
 * if(a[i]==a[j]) dp[i][j]=dp[i-1][j-1]+1;
 * else           dp[i][j]=max(dp[i-1][j],dp[i][j-1])
 *      i
 *      1  2  3  4  5  6  7
 *   s1 A  B  C  B  D  A  B
 *      j
 *      1  2  3  4  5  6
 *   s2 B  D  C  A  B  A
 *   dp 1  2  3  4  5  6
 *   1  0  0  0  1  1  1
 *   2  1  1  1  1  2  2
 *   3  1  1  2  2  2  2
 *   4  1  1  2  2  3  3
 *   5  1  2  2  2  3  3
 *   6  1  2  2  3  3  4
 *   7  1  2  2  3  4  4
 * */
const int N=1e3+10;
int dp[N][N];
int main(){
    string s1,s2;
    cin>>s1>>s2;
    int n=s1.size(),m=s2.size();
    s1=' '+s1;s2=' '+s2;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1;
            else           dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    cout<<dp[n][m]<<endl;
    return 0;
}

最短编辑距离

#include<iostream>
using namespace std;
/*
多序列dp-编辑距离Edit-Distance
1.状态  dp[i][j] s1的前i个字符变为s2的前j个字符所需的最小操作数(即最短编辑距离)
2.状态转移方程
if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]
else dp[i][j]=min{dp[i-1][j]删除,dp[i][j-1]添加, dp[i - 1][j - 1]直接修改} + 1消耗一次操作数
*/
/*
 *        i
 *        1  2  3  4  5  6  7
 *    s1  s  f  d  q  x  b  w
 *        j
 *        1  2  3  4  5
 *    s2  g  f  d  g  w
 *    dp  1  2  3  4  5
 *    1
 *    2
 *    3
 *    3
 *    4
 *    5
 *    6
 *    7
 * */
// AB A
const int N = 2e3 + 10;
int dp[N][N];
int main() {
    //边界   dp[i][0]=i    dp[0][j]=j
    string s1, s2;
    cin >> s1 >> s2;
    int n = s1.size(), m = s2.size();
    s1 = ' ' + s1;  s2 = ' ' + s2;//下标从1开始

    //边界
    for (int i = 1; i <= n; i++) dp[i][0] = i;
    for (int j = 1; j <= m; j++) dp[0][j] = j;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1];
            else dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
            //                       删除			增加			x
        }
    }
    cout << dp[n][m] << endl;
    return 0;
}

01背包问题

特点

n种物品,每种物品只有一件,要么放,要么不放
 * dp[i][j]-->前i件物品在背包容量不超过j的前提下产生的最大价值
 * 背包容量不够,不放0
 * if(j<w[j]) dp[i][j]=dp[i-1][j]
 * 背包容量够,放不放都行,max(0,1)决策
 * else       dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j])

搜索

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

const int N=1e2+10;
int m,n,w[N],v[N],mx=INT_MIN;
bool vis[N];

void dfs(int curv,int curw,int depth){
    if(curw>m) return;
    mx=max(mx,curv);
    if(depth==n+1) return;

    for(int i=1;i<=n;i++){
        if(!vis[i]){
            vis[i]=1;
            dfs(curv+v[i],curw+w[i],depth+1);
            vis[i]=0;
        }
    }
}

int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    dfs(0,0,1);
    cout<<mx<<endl;
    return 0;
}

二维01背包DP

#include <iostream>
#include <climits>

using namespace std;

const int N=35,M=2e2+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[N][M];
/*
 * dp[i][j]-->前i件物品在背包容量不超过j的前提下产生的最大价值
 * 背包容量不够,不放0
 * if(j<w[j]) dp[i][j]=dp[i-1][j]
 * 背包容量够,放不放都行,max(0,1)决策
 * else       dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j])
 * */


int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(j<w[i]) dp[i][j]=dp[i-1][j];
            else       dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
        }
    }
    cout<<dp[n][m]<<endl;
    return 0;
}

01背包的逆推滚动数组优化

dp数组定义的时候省去了i,由于是自己转移给自己,需要逆推转移
#include <iostream>
#include <climits>

using namespace std;

const int N=35,M=2e2+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[M];
/*
 * 01 背包的滚动数组优化,自己转移给自己
 * 由于是自己转移给自己,如果前面的先被修改了后面的就都错了
 * */
int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

完全背包

特点

特点:n种物品,每种物品有无限件(但其实是有限 m/w[i])
dp[j]=max{
			dp[j-0*w[i]]+0*v[i],
         dp[j-1*w[i]]+1*v[i],
			dp[j-2*w[i]]+2*v[i],
			dp[j-3*w[i]]+3*v[i],
			...
			dp[j-m/w[i]*w[i]]+m/w[i]*v[i]
		 }

逆推朴素版本

#include <iostream>
#include <climits>

using namespace std;

const int N=35,M=2e2+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[M];
//完全背包
//特点:n种物品,每种物品有无限件(但其实是有限 m/w[i])
//状态 dp[j] 前i种物品在背包容量不超过j的情况下的最大价值
//状态转移方程
/*
dp[j]=max{
			dp[j-0*w[i]]+0*v[i],
            dp[j-1*w[i]]+1*v[i],
			dp[j-2*w[i]]+2*v[i],
			dp[j-3*w[i]]+3*v[i],
			...
			dp[j-m/w[i]*w[i]]+m/w[i]*v[i]
		 }
*/
int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--){
            for(int k=1;k<=m/w[i];k++){
                //对第i种物品选k件取决策
                if(j>=k*w[i])dp[j]=max(dp[j-k*w[i]]+k*v[i],dp[j]);
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

正推叠加优化版本

#include <iostream>
#include <climits>

using namespace std;

const int N=40,M=2e2+10;
int m,n,w[N],v[N],dp[M];
//完全背包
//特点:n种物品,每种物品有无限件(但其实是 有限 m/w[i])
//状态 dp[j] 前i种物品在背包容量不超过j的情况下的最大价值
//状态转移方程
/*
dp[j]=max{
			dp[j-0*w[i]]+0*v[i],
             dp[j-1*w[i]]+1*v[i],
			dp[j-2*w[i]]+2*v[i],
			dp[j-3*w[i]]+3*v[i],
			...
			dp[j-m/w[i]*w[i]]+m/w[i]*v[i]
		 }
*/
int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=n;i++){
        //完全背包z叠加优化
        for(int j=w[i];j<=m;j++){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    cout<<"max="<<dp[m]<<endl;
    return 0;
}

多重背包

特点

n种物品,每种都有固定的个数

朴素版本

#include <iostream>
#include <climits>

using namespace std;

const int N=5e2+10,M=6e3+10;
int m,n,w[N],v[N],mx=INT_MIN,dp[M],s[N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i]>>s[i];
        s[i]=min(s[i],m/w[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--){
            //for(int k=0;k<=1;k++){}0 1 背包
            for(int k=1;k<=s[i];k++){
                //对第i种物品选k件取决策
                if(j>=k*w[i])dp[j]=max(dp[j-k*w[i]]+k*v[i],dp[j]);
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

二进制分解物品数优化

#include<iostream>
using namespace std;
//注意,二进制分解后物品种类会增多,所以N的数量级需要开大一些
const int N = 5e3 + 10, M = 6e3 + 10;
int m, n, w[N], v[N], s[N], dp[M];
int ww, vv, ss;
int main() {
	cin >> n >> m;
	int id = 0;//二进制分解后的物品的种类
	//二进制分解,将第i种物品的s件拆分成若干件物品,每件物品只有一件,转换为01背包问题
	for (int i = 1; i <= n; i++) {
		cin >> ww >> vv >> ss;//输入每种物品的容量、价值、数量
		
		//针对ss进行二进制分解
		for (int j = 1; j <= ss;j<<=1) {
			w[++id] = j * ww;
			v[id] = j * vv;
			ss -= j;
		}
		//如果二进制分解后的ss有剩余,则按剩余ss倍的物品存储
		if (ss) {
			w[++id] = ss * ww;
			v[id] = ss * vv;
			ss = 0;
		}
	}
	//注意,二进制分解后的物品种类不再是n,是id
	for (int i = 1; i <= id; i++) {
		for (int j = m; j >= w[i]; j--) {
			dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
		}
	}
	
	cout << dp[m] << endl;
	return 0;
}

混合背包

转化为多重背包问题

#include <iostream>

using namespace std;
const int N=40,M=2e2+10;
int w[N],v[N],s[N],m,n,dp[M];

int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i]>>s[i];
        if(s[i]==0) s[i]=m/w[i];
        if(s[i]!=0&&s[i]!=1){
            s[i]=min(s[i],m/w[i]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--){
            for(int k=1;k<=s[i];k++){
                if(j>=k*w[i]) dp[j]=max(dp[j],k*v[i]+dp[j-k*w[i]]);
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

转化为01背包问题

#include<iostream>
using namespace std;
//注意,二进制分解后物品种类会增多,所以N的数量级需要开大一些
const int N = 100, M = 400;
int m, n, w[N], v[N], s[N], dp[M];
int ww, vv, ss;
int main() {
    cin >> m >> n;
    int id = 0;//二进制分解后的物品的种类
    //二进制分解,将第i种物品的s件拆分成若干件物品,每件物品只有一件,转换为01背包问题
    for (int i = 1; i <= n; i++) {
        cin >> ww >> vv >> ss;//输入每种物品的容量、价值、数量
        if(ss==0) ss=m/ww;
        //针对ss进行二进制分解
        for (int j = 1; j <= ss;j<<=1) {
            w[++id] = j * ww;
            v[id] = j * vv;
            ss -= j;
        }
        //如果二进制分解后的ss有剩余,则按剩余ss倍的物品存储
        if (ss) {
            w[++id] = ss * ww;
            v[id] = ss * vv;
            ss = 0;
        }
    }
    //注意,二进制分解后的物品种类不再是n,是id
    for (int i = 1; i <= id; i++) {
        for (int j = m; j >= w[i]; j--) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }

    cout << dp[m] << endl;
    return 0;
}

二维费用背包

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

/*
二维费用背包(基础就是01背包)
状态dp[j][k] 前i种物品在背包1容量不超过j的情况且背包2容量不超过k的情况下构成的最小价值
状态转移方程  dp[j][k]=min(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]);
*/
const int N = 1e3 + 10, M = 1e2 + 10;
int n, m1, m2;
int w1[N], w2[N], v[N], dp[M][M];
int main() {

    cin >> m1 >> m2 >> n;
    for (int i = 1; i <= n; i++)
        cin >> w1[i] >> w2[i] >> v[i];

    //注意,本题求最小价值,dp一定初始化为最大
    memset(dp, 0x3f, sizeof dp);
    dp[0][0] = 0;

    for (int i = 1; i <= n; i++)
        for (int j = m1; j >= 0; j--)
            for (int k = m2; k >= 0; k--)
                dp[j][k] = min(dp[j][k], dp[max(0,j - w1[i])][max(0,k - w2[i])] + v[i]);

    cout << dp[m1][m2] << endl;
    return 0;
}

分组背包

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

/*
二维费用背包(基础就是01背包)
状态dp[j][k] 前i种物品在背包1容量不超过j的情况且背包2容量不超过k的情况下构成的最小价值
状态转移方程  dp[j][k]=min(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]);
*/
const int N = 40, M = 210;
int n, m,p,t;
int w[N], v[N], dp[M];
int main() {
    cin>>m>>n>>t;
    vector<int> group[N];
    for (int i = 1; i <= n; i++){
        cin >> w[i] >> v[i] >>p;
        group[p].push_back(i);
    }
    for (int i = 1; i <= t; i++)
        for (int j = m; j >= 1; j--){
            for(int k=0;k<group[i].size();k++){
                int id=group[i][k];
                if(j>=w[id]) dp[j]=max(dp[j],v[id]+dp[j-w[id]]);
            }
        }

    cout << dp[m] << endl;
    return 0;
}

有依赖背包

特点:

有主件有附件,每种物品只有一件

要么不选主件

要么选主件不选附件

要么选主键和第一件附件

要么选主键和第二件附件

要么选主键、第一件附件和第二件附件

5中情况之中区max

[P1064 NOIP2006 提高组] 金明的预算方案 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <iostream>


using namespace std;
const int N=100,M=1e5+10;
int m,n,main_w[N],main_v[N],sec_w[N][3],sec_v[N][3],dp[M],cnt[N];

int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        int ww,vv,q;
        cin>>ww>>vv>>q;
        if(q==0){//主件
            main_w[i]=ww;
            main_v[i]=ww*vv;
        }
        else{//附件,那么q附件对应就是主要的编号
            cnt[q]++;//第q个主件的第cnt[q]个附件
            sec_w[q][cnt[q]]=ww;
            sec_v[q][cnt[q]]=ww*vv;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1;j--){
            //选主件,不选附件
            if(j>=main_w[i]) dp[j]=max(dp[j],main_v[i]+dp[j-main_w[i]]);
            //选主键和第一个附件
            if(j>=main_w[i]+sec_w[i][1]) dp[j]=max(dp[j],main_v[i]+sec_v[i][1]+dp[j-(main_w[i]+sec_w[i][1])]);
            //选主键和第二个附件
            if(j>=main_w[i]+sec_w[i][2]) dp[j]=max(dp[j],main_v[i]+sec_v[i][2]+dp[j-(main_w[i]+sec_w[i][2])]);
            //选主键和第一个附件和第二个附件
            if(j>=main_w[i]+sec_w[i][1]+sec_w[i][2]) dp[j]=max(dp[j],main_v[i]+sec_v[i][1]+sec_v[i][2]+dp[j-(main_w[i]+sec_w[i][1]+sec_w[i][2])]);
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

背包求方案数

背包

#include <iostream>

using namespace std;
const int N=1e5+10;
int m,n,w[N],dp[N];
//01背包求方案数
//dp[j]-->前i个数组组合出数字j的方案数

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    dp[0]=1;
    for(int i=1;i<=n;i++){
        for(int j=m;j>=w[i];j--){
            dp[j]=d[j]+dp[j-w[i]];
            //    不选第i个+x
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

dfs

#include <iostream>

using namespace std;
const int N=25;
int a[N],ans=0,n,vis[N],cnt[N],sum;
void dfs(int depth,int cursum,int start){
    if(cursum>sum||depth>n) return;
    if(cursum==sum){
        
        ans++;
        return;
    }
    for(int i=start;i<=n;i++){
        if(!vis[i]){
            vis[i]=1;
            cnt[depth]=a[i];
            dfs(depth+1,cursum+a[i],i);
            vis[i]=0;
        }
    }
}
int main(){
    cin>>n>>sum;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    dfs(1,0,1);
    if(sum==0) cout<<1<<endl;
	else cout<<ans<<endl;
    return 0;
}

背包求具体方案

#include<iostream>
using namespace std;
const int N = 40, M = 2e2 + 10;
int m, n, w[N], v[N], dp[N][M];//注意求具体方案时,需要二维dp
//背包求具体方案
int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (j < w[i]) dp[i][j] = dp[i - 1][j];//放不下,不放

			else dp[i][j] = max(dp[i - 1][j], dp[i-1][j - w[i]] + v[i]);
		}
	}
	//cout << dp[n][m] << endl;
	//打印具体方案
	int j = m;
	for (int i = n; i >= 1; i--) {
		if (dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
			cout << i << " ";
			j -= w[i];
		}
	}
	return 0;
}

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值