背包九讲 附Acwing模板题AC代码

推荐博客

背包九讲原文
背包九讲——全篇详细理解与代码实现
本博客对基础内容不进行解释,基础薄弱的可以与上述博客一起食用。

01 01 背包

例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;

int n,m;
int v[maxn],w[maxn],dp[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
        scanf("%d%d",&w[i],&v[i]);//体积 价值
    for(int i=0;i<n;i++)
        for(int j=m;j>=w[i];j--)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    printf("%d\n",dp[m]);
    return 0;
}

完全背包

在这里插入图片描述可以不写这个优化,但是思想要理解,下面也会用到。
例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;

int n,m;
int v[maxn],w[maxn],dp[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
        scanf("%d%d",&w[i],&v[i]);//体积 价值
    for(int i=0;i<n;i++)
        for(int j=w[i];j<=m;j++)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    printf("%d\n",dp[m]);
    return 0;
}

多重背包

朴素版

例题
t i p s tips: 太慢了,基本不会用这种方法。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=105;

int n,m;
int v[maxn*maxn],w[maxn*maxn],dp[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    int len=0,s;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d",&w[len],&v[len],&s);//体积 价值 数量
        ++len,--s;
        while(s--)
            w[len]=w[len-1],v[len]=v[len-1],++len;
    }
    for(int i=0;i<len;i++)
        for(int j=m;j>=w[i];j--)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    printf("%d\n",dp[m]);
    return 0;
}

二进制优化

基本思想:把 n n 件物品用二进制形式表示出来,即 1 , 2 , 4 , , 2 k 1 , n + 1 2 k 1,2,4,……,2^{k-1},n+1-2^k 。且 k k 满足: n < 2 k + 1 1 n<2^{k+1}-1
正确性证明:首先 1 , 2 , 4 , , 2 k 1 1,2,4,……,2^k-1 可以表示 [ 1 , 2 k 1 ] [1,2^k-1] 的任意数,两端都加上 n + 1 2 k n+1-2^k 后可以表示 [ n + 2 2 k , n ] [n+2-2^k,n] 的任意数,由于 n < 2 k + 1 1 n<2^{k+1}-1 ,所以 n + 2 2 k < 2 k + 1 1 + 2 2 k = 2 k + 1 n+2-2^k<2^{k+1}-1+2-2^k=2^k+1 ,即 n + 2 2 k < = 2 k n+2-2^k<=2^k ,所以这些数可以表示的范围为 [ 1 , n ] [1,n]
这个应该是最常用的优化了,复杂度为 O ( V i = 1 n l o g ( n u m [ i ] ) ) O(V*\sum_{i=1}^{n}log(num[i]))

例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=2005;

int n,m;
int v[10*maxn],w[10*maxn],dp[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    int len=0,a,b,c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d",&a,&b,&c);//体积 价值 数量
        int tmp=1;
        while(tmp<=c)
        {
            c-=tmp;
            w[len]=tmp*a;
            v[len++]=tmp*b;
            tmp<<=1;
        }
        w[len]=c*a;
        v[len++]=c*b;
    }
    for(int i=0;i<len;i++)
        for(int j=m;j>=w[i];j--)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    printf("%d\n",dp[m]);
    return 0;
}

单调队列优化

证明:这个写的挺好的,倒数第 2 6 7 2、6、7 行有一点点笔误。
倒数第 2 2 行,应该是 f [ i ] [ j ] = m a x ( f [ i 1 ] [ j    m o d    w + k w ] k v ) + j / w v f[i][j]=max(f[i-1][j\ \ mod\ \ w+k*w]-k*v)+j/w*v
倒数第 6 7 6、7 行开始的 + w +w 应该修改为 + v +v
在这里插入图片描述确实比较难理解,不过优化很给力,复杂度为 O ( n V ) O(n*V)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=2e4+5;

struct node //单调队列要用到
{
    int idx,val;
    node(){}
    node(int i,int v):idx(i),val(v){}
}q[maxn];

int n,m;
int dp[maxn];

inline void cal(int v,int w,int c)//体积 价值 数量
{
    int fon,bak,tmp;
    for(int i=0;i<v;i++)
    {
        fon=bak=0;//fon=bak 时队列为空 fon<bak 时队列非空
        for(int j=i,k=0;j<=m;j+=v,++k)
        {
            tmp=dp[j]-k*w;
            if(fon<bak&&k-q[fon].idx>c)
                ++fon;
            while(fon<bak&&tmp>=q[bak-1].val)
                --bak;
            q[bak++]=node(k,tmp);
            dp[j]=q[fon].val+k*w;
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    int a,b,c;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d",&a,&b,&c);//体积 价值 数量
        cal(a,b,c);
    }
    printf("%d\n",dp[m]);
    return 0;
}

混合背包

例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;

int n,m;
int w[maxn],v[maxn],dp[maxn];

int main()
{
    scanf("%d%d",&n,&m);
    int s,len;
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d",&w[i],&v[i],&s);//体积 价值 数量
        if(s==-1) //01背包
        {
            for(int j=m;j>=w[i];j--)
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
        else if(s==0) //完全背包
        {
            for(int j=w[i];j<=m;j++)
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
        else //多重背包
        {
            int a=w[i],b=v[i],tmp=1;
            len=0;
            while(tmp<=s)
            {
                s-=tmp;
                w[len]=a*tmp;
                v[len++]=b*tmp;
                tmp<<=1;
            }
            w[len]=a*s;
            v[len++]=b*s;
            for(int j=0;j<len;j++)
                for(int k=m;k>=w[j];k--)
                    dp[k]=max(dp[k],dp[k-w[j]]+v[j]);
        }
    }
    printf("%d\n",dp[m]);
    return 0;
}

二维费用背包问题

解决方法同一维背包,只不过数组多了一维。
在这里插入图片描述例题

扫描二维码关注公众号,回复: 9654735 查看本文章
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;

int n,V,M;
int v[maxn],m[maxn],w[maxn],dp[maxn][maxn];

int main()
{
    scanf("%d%d%d",&n,&V,&M);//数量 背包容积 背包可承重量
    for(int i=0;i<n;i++)
        scanf("%d%d%d",&v[i],&m[i],&w[i]);//体积 重量 价值
    for(int i=0;i<n;i++) //01 二维背包
        for(int j=V;j>=v[i];j--)
            for(int k=M;k>=m[i];k--)
                dp[j][k]=max(dp[j][k],dp[j-v[i]][k-m[i]]+w[i]);
    printf("%d\n",dp[V][M]);
    return 0;
}

分组背包

在这里插入图片描述
例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=105;

int n,m;
int w[maxn][maxn],v[maxn][maxn],dp[maxn],s[maxn];

int main()
{
    scanf("%d%d",&n,&m);//物品组数 背包容量
    for(int i=0;i<n;i++)
    {
        scanf("%d",&s[i]);//第i组物品的物品数量
        for(int j=0;j<s[i];j++)
            scanf("%d%d",&w[i][j],&v[i][j]);//体积 价值
    }
    for(int i=0;i<n;i++)//第i组
        for(int j=m;j>=0;j--)//注意循环顺序 保证该组只选1个
            for(int k=0;k<s[i];k++)
                if(j>=w[i][k])
                    dp[j]=max(dp[j],dp[j-w[i][k]]+v[i][k]);
    printf("%d\n",dp[m]);
    return 0;
}

有依赖背包问题

在这里插入图片描述上图算法这一块内容非常关键,建议多读几遍好好体会。
在这里插入图片描述例题
复杂度 O ( n V 2 ) O(n*V^2)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=105;

struct Edge
{
    int to,nxt;
}edge[maxn<<1];

int n,m,tot;
int head[maxn];
int val[maxn],w[maxn],dp[maxn][maxn];
    //dp[i][j]表示在以i为根的子树中 背包容量为j时的最优解
inline void addedge(int u,int v)
{
    edge[++tot].to=v,edge[tot].nxt=head[u],head[u]=tot;
}

void dfs(int u,int fa)
{
    int v;
    for(int i=head[u];i;i=edge[i].nxt)
    {
        v=edge[i].to;
        if(v==fa)
            continue;
        dfs(v,u);//递归处理子节点
        for(int j=m-w[u];j>=w[v];j--)//分组背包 由于u必选 所以j的上限为m-w[u]
            for(int k=j;k>=w[v];k--) //由于v必选 所以k的下限为w[v]
                dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
    for(int j=m;j>=w[u];j--) //有依赖关系 所以必须选上节点u
        dp[u][j]=dp[u][j-w[u]]+val[u];
}

int main()
{
    scanf("%d%d",&n,&m);
    int s,root;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&w[i],&val[i],&s);//体积 价值 依赖的物品编号
        if(s==-1)
            root=i;
        else
            addedge(s,i),addedge(i,s);
    }
    dfs(root,root);
    printf("%d\n",dp[root][m]);
    return 0;
}

泛化物品

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

背包问题问法的变化

输出字典序最小的最优方案

在这里插入图片描述感觉上面有一处笔误,倒数第二行应该是 f [ i ] [ j ] = = f [ i 1 ] [ j ] f[i][j]==f[i-1][j]
例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;

int n,m;
int v[maxn],w[maxn],dp[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=n;i>=1;i--) //逆序读入
        scanf("%d%d",&w[i],&v[i]); //体积 价值
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=w[i];j--)
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        for(int j=w[i]-1;j>=0;j--)
            dp[i][j]=dp[i-1][j];
    }
    int val=m;
    for(int i=n;i>=1;i--)
    {
        if(val>=w[i]&&dp[i][val]==dp[i-1][val-w[i]]+v[i])
        {
            val-=w[i];
            printf("%d ",n-i+1);
        }
    }
    return 0;
}

输出最优解的方案数

在这里插入图片描述例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;
const int mod=1e9+7;

int n,m;
int v[maxn],w[maxn],dp[maxn],num[maxn];

//dp[i]表示恰好装满体积为i的背包时所能得到的最大价值
//num[i]表示背包内物品体积之和为i时的方案数
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) //逆序读入
        scanf("%d%d",&w[i],&v[i]); //体积 价值
    memset(dp,-INF,sizeof(dp));
    dp[0]=0,num[0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=w[i];j--)
        {
            if(dp[j]<dp[j-w[i]]+v[i])
            {
                dp[j]=dp[j-w[i]]+v[i];
                num[j]=num[j-w[i]];
            }
            else if(dp[j]==dp[j-w[i]]+v[i])
                num[j]=(num[j]+num[j-w[i]])%mod;
        }
    }
    int ans=0,MAX=0;
    for(int i=1;i<=m;i++)
        MAX=max(MAX,dp[i]);
    for(int i=1;i<=m;i++)
        if(dp[i]==MAX)
            ans=(ans+num[i])%mod;
    printf("%d\n",ans);
    return 0;
}

k k 优解

在这里插入图片描述

int kth(int n, int V, int k) {
    for (int i = 1; i <= n; i++) {
        for (int j = V; j >= w[i]; j--) {
            for (int l = 1; l <= k; l++) {
                a[l] = f[j][l];
                b[l] = f[j - w[i]][l] + v[i];
            }
            a[k + 1] = -1;
            b[k + 1] = -1;
            int x = 1, y = 1, o = 1;
            while (o != k + 1 and (a[x] != -1 or b[y] != -1)) {
                if (a[x] > b[y]) f[j][o] = a[x], x++;
                else f[j][o] = b[y], y++;
                if (f[j][o] != f[j][o - 1]) o++;
            }
        }
    }
    return f[V][k];
}

例题

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

const int maxn=1005;

int n,m,k;
int v[maxn],w[maxn],dp[maxn][35];
int a[maxn],b[maxn];

void kth(int n,int V,int k)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=V;j>=w[i];j--)
        {
            for(int l=1;l<=k;l++)
                a[l]=dp[j][l],b[l]=dp[j-w[i]][l]+v[i];
            a[k+1]=-1,b[k+1]=-1;
            int x=1,y=1,id=1;
            while(id<=k&&(a[x]!=-1||b[y]!=-1))
            {
                if(a[x]>b[y])
                    dp[j][id]=a[x],++x;
                else
                    dp[j][id]=b[y],++y;
                if(dp[j][id]!=dp[j][id-1])
                    ++id;
            }
        }
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n;i++)
            scanf("%d",&v[i]); //价值
        for(int i=1;i<=n;i++)
            scanf("%d",&w[i]); //体积
        kth(n,m,k);
        printf("%d\n",dp[m][k]);
    }
    return 0;
}

发布了677 篇原创文章 · 获赞 30 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/xiji333/article/details/104226993