背包

背包

blog背包九讲

n件物品,容量为V的背包,w价值,v体积

$ dp[i][j]=dp[i-1][j-k*v[i]]+k*w[i] $

01背包

每个物品只有一件 k=1

memset(f,0xcf,sizoef(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
    for(int j=V;j>=v[i];j--)//注意是倒序 
        f[j]=max(f[j],f[j-v[i]]+w[i]);

完全背包

每种物品使用无限次

for(i=1;i<=n;i++)
    for(j=v[i];j<=V;j++)//物品用了可以再用,不用考虑i;注意,这里是“顺序 ”
        f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[V]<<endl;

多重背包

i 种物品有 n[ i ]

O(V*∑logn[i])

摆花

记忆化搜索还是很好理解,虽然复杂度难以描述

#include <cstdio>
#include <iostream>
using namespace std;
int dp[105][105],n,m;
const int mod=1000007;
int a[105];
int dfs(int i,int j) {
    if(j>m) return 0;
    if(j==m) return 1;
    if(i>n) return 0;
    if(dp[i][j]) return dp[i][j];
    int ans=0;
    for(int k=0;k<=a[i];k++)
        (ans+=dfs(i+1,j+k))%=mod;
    dp[i][j]=ans;
    return ans;    
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    printf("%d
",dfs(1,0));
    return 0;
}

然后我们考虑dp

推出三重for的式子

#include <cstdio>
#include <iostream>
using namespace std;
int dp[105][105],n,m;
const int mod=1000007;
int a[105];
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k<=min(j,a[i]);k++)
                (dp[i][j]+=dp[i-1][j-k])%=mod;
    printf("%d
",dp[n][m]);
    return 0;
}

这其实就是多重背包最原始的样子

然后发现 i 只和 i-1 有关,我们搞个滚动数组

dp[0][0]=1;
for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++){
        dp[i&1][j]=0;//莫忘初始化
        for(int k=0;k<=min(j,a[i]);k++)
            (dp[i&1][j]+=dp[i&1^1][j-k])%=mod;
    }
printf("%d
",dp[n&1][m]);

然后,,,这不就是个01背包吗

直接压成01背包

f[0] = 1;
for(int i=1; i<=n; i++)
    for(int j=m; j>=0; j--) 
        for(int k=1; k<=min(a[i], j); k++)
            f[j] = (f[j] + f[j-k])%mod;
cout<<f[m]<<endl;

二进制拆分

做法:把每一个物品根据2的多少次方拆分,因为任何数都可以转化为二进制数

核心思想:把每一个物品拆成很多个,分别计算价值和所需时间,再转化为01背包求解

#include <iostream>
#include <cstdio>
using namespace std;
inline int read() {
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f; 
}
const int N=100005;
int n,h1,h2,t1,t2,m;
int v[N],w[N],c[N],f[1005];
int cost[N],val[N],tp;//开大
void pre() {
    for(int i=1;i<=n;i++) {
        int t=1;
        while(c[i]) {
            cost[++tp]=t*v[i];
            val[tp]=t*w[i];
            c[i]-=t;
            t*=2;
            if(c[i]<t) {//如果剩下的不能再拆,就直接放在一起
                cost[++tp]=c[i]*v[i];
                val[tp]=c[i]*w[i];
                break;
            }
        }
    }
}
int main() {
    scanf("%d:%d%d:%d",&h1,&t1,&h2,&t2);
    m=(h2*60+t2)-(h1*60+t1);
    n=read();
    for(int i=1;i<=n;i++) {
        v[i]=read();w[i]=read();c[i]=read();
        if(!c[i]) c[i]=99999;
    }
    pre();
    for(int i=1;i<=tp;i++)
        for(int j=m;j>=cost[i];j--)
            f[j]=max(f[j],f[j-cost[i]]+val[i]);
    printf("%d
",f[m]);
    return 0;   
}

单调队列优化 O(n*V)

https://www.cnblogs.com/-guz/p/9866118.html

蓝书P341 讲的很好

image-20200718204945938

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read() {
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f; 
}
const int N=1e5+10;
int n,m,f[N],q[N];
int w[N],v[N],c[N];
int calc(int i,int d,int k) {
    return f[d+k*v[i]]-k*w[i];
}
int main() {
    memset(f,0xcf,sizeof(f));
    f[0]=0;
    n=read();m=read();
    for(int i=1;i<=n;i++) {
        w[i]=read();v[i]=read();c[i]=read();
        for(int d=0;d<v[i];d++) {
            int l=1,r=0,maxp=(m-d)/v[i];
            for(int k=maxp-1;k>=max(maxp-c[i],0);k--) {
                while(l<=r&&calc(i,d,q[r])<=calc(i,d,k)) r--;
                q[++r]=k;
            }
            for(int p=maxp;p>=0;p--) {
                while(l<=r&&q[l]>p-1) l++;
                if(l<=r) f[d+p*v[i]]=max(calc(i,d,q[l])+p*w[i],f[d+p*v[i]]);
                if(p-c[i]-1>=0) {
                    while(l<=r&&calc(i,d,q[r])<=calc(i,d,p-c[i]-1)) r--;
                    q[++r]=p-c[i]-1;
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=m;i++)
        ans=max(ans,f[i]);
    printf("%d
",ans);
    return 0;
}

混合背包

有一个具有n种货币的货币系统, 每种货币的面值为val[i]. 现在小杰手上拿着num[1],num[2],…num[n]个第1种,第2种…第n种货币
去买价值为T(T<=20000)的商品, 他给售货员总价值>=T的货币,然后售货员(可能,如果小杰给的钱>T,那肯定找钱)找钱给他.
售货员每次总是用最少的硬币去找钱给小杰. 现在的问题是: 小杰买价值T的商品时, 他给售货员的硬币数目+售货员找他的硬币数目最少等于多少?

第一行先输入n和c,n代表硬币的种类数,c代表顾客需要付的款数,
第二行是顾客n种硬币的面值
第三行是顾客相应的n种硬币的个数

顾客和店员的区别:
具有的硬币的种类数都是n种,不过顾客的硬币数是有限的(输入的相应硬币数目),店员的硬币数目是无限的
现在顾客需要买c元的东西,只能用这些硬币付款,且顾客手中不同种类的硬币的数目是有限的,所以现在顾客付款有两种情况:
1.店员不需要找钱 顾客手中的硬币种类和个数刚好可以组成c元
2.店员需要找钱,顾客手中硬币种类和个数不可以恰好组成c元,所以顾客只能给T元,店员给顾客找钱c-T元
现在要 求经过店员和顾客手中的硬币的最小数目

解题思路;
主要的方向就是对店员进行完全背包从操作,对顾客进行多重背包的操作,然后最后遍历两个dp数组,找到最小硬币数目(t=min(t,dp2[i]+dp1[i-c]))
需要注意的地方:
dp数组初始化
因为是要能付款,而不是求硬币数目最少,所以是"恰恰好装满背包“
所以两个数组除了dp[0]以外,都要赋无穷大

#include<bits/stdc++.h>
using namespace std;
int n,V,v[10005],w[10005],s[10005],f[21000],h=0;
template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
    x*=f;
}
int main()
{
    read(n);read(V);
    for(int i=1;i<=n;i++){
        int x,y,z,t;
        read(x),read(y),read(z);
        if(z<0)               
            c[++h]=x,w[h]=y,s[h]=1;
        else if(z==0)
            c[++h]=x,w[h]=y,s[h]=0;
        else if (z > 0) {
            t=1;
            while(t<=z){
                c[++h]=t*x;
                w[h]=t*y;
                s[h]=1;
                z-=t;
                t<<=1;
            }
            c[++h]=z*x;
            w[h]=z*y;
            s[h]=1;
        }
    }
    for(int i=1;i<=h;i++){//注意这里是h,不是n     
        if(s[i]==0){ //完全         
            for(int j=c[i];j<=v;j++)
            f[j]=max(f[j],f[j-c[i]]+w[i]);
        }
        else if(s[i]==-1||s[i]==1){//01        
            for(int j=v;j>=c[i];j--)
            f[j]=max(f[j],f[j-c[i]]+w[i]);
        }
    }
    cout<<f[V]<<endl;
    return 0;
 } 

分组背包

https://www.acwing.com/problem/content/9/

#include <iostream>
#include <cstdio>
using namespace std;
const int N=105;
int f[N],n,m,s[N],v[N][N],w[N][N];
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        scanf("%d",&s[i]);
        for(int j=1;j<=s[i];j++)
            scanf("%d%d",&v[i][j],&w[i][j]);
    }
    for(int i=1;i<=n;i++) 
        for(int j=m;j>=0;j--)
            for(int k=1;k<=s[i];k++)
                if(j>=v[i][k])
                    f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
    printf("%d
",f[m]);
    return 0;
}

有依赖的背包

金明的预算方案

#include <iostream>
#include <cstdio>
using namespace std;
const int N=5000010;
int n,m;
struct node{
    int v,w,q;
}a[N];
int h[N],f[N];
int main() {
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) 
        scanf("%d%d%d",&a[i].v,&a[i].w,&a[i].q),a[i].w*=a[i].v;
    for(int i=1;i<=n;i++) {
        if(!a[i].q) {
            for(int j=1;j<a[i].v;j++) h[j]=0;
            for(int j=a[i].v;j<=m;j++)
                h[j]=f[j-a[i].v]+a[i].w;
            for(int j=1;j<=n;j++)
                if(a[j].q==i) {
                    for(int k=m;k>=a[i].v+a[j].v;k--)
                        h[k]=max(h[k],h[k-a[j].v]+a[j].w);
                }
            for(int j=a[i].v;j<=m;j++)
                f[j]=max(f[j],h[j]);
        }
    }
    printf("%d
",f[m]);
    return 0;
}

https://www.acwing.com/problem/content/description/10/

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N=105;
int f[N][N],n,m,root,v[N],w[N];
vector<int>g[N];
void dfs(int x) {
    for(int i=v[x];i<=m;i++) 
        f[x][i]=w[x];//点x必须选
    for(int i=0;i<g[x].size();i++) {
        int y=g[x][i];
        dfs(y);
        for(int j=m;j>=v[x];j--)
            for(int k=0;k<=j-v[x];k++)//子树的体积
                f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1,fa;i<=n;i++) {
        scanf("%d%d%d",&v[i],&w[i],&fa);
        if(fa==-1) root=i;
        else g[fa].push_back(i);
    }
    dfs(root);
    printf("%d
",f[root][m]);
    return 0;
}

https://oi-wiki.org/dp/knapsack/

题目:

疯狂的火神

这很显然是个01背包,体积为 c[] , 价值 $a[]-b[]*x $,我们怎么搞这个时间呢?

设$ i$ 优于 (j) 先选,已经用时(time),那么 $ a[i]-b[i]*time+a[j]-b[j]*time+>a[j]-b[j]*time+a[i]-b[i]*(time+c[j]) $

化简得$ c[i]*b[j] < b[i]*c[j] $

#include <bits/stdc++.h>
using namespace std;
const int maxt=2147483647;
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}
int f[3010],ans=0;
int t,n,m;
struct node{
	int a,b,c;
}x[1005];
bool cmp(node x,node y){
	return x.c*y.b<x.b*y.c;
}
int main(){
	t=read();
	while(t--){
		memset(f,0,sizeof(f));
		ans=-maxt;
		n=read(),m=read();
		for(int i=1;i<=n;i++)
		x[i].a=read(),x[i].b=read(),x[i].c=read();
		sort(x+1,x+1+n,cmp);
		for(int i=1;i<=n;i++)
			for(int j=m;j>=x[i].c;j--){ 
				f[j]=max(f[j],f[j-x[i].c]+x[i].a-x[i].b*j);
				ans=max(ans,f[j]);
			}
		printf("%d
",ans);
	}
	return 0;
}

poj1015

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

int n,m,cas=1,fix,p,d,S,A,P,D;
int dp[25][805],sub[205],add[205];
vector<int> path[25][805];

int main(){
    while(~scanf("%d%d",&n,&m)&&n){
        memset(dp,-1,sizeof(dp));
        for(int i=1;i<=m;++i)
            for(int j=0;j<805;++j)
                path[i][j].clear();
        fix=20*m;
        dp[0][fix]=0;
        for(int i=1;i<=n;++i){
            scanf("%d%d",&p,&d);
            sub[i]=p-d;
            add[i]=p+d;
        }
        for(int i=1;i<=n;++i)
            for(int j=m-1;j>=0;--j)
                for(int k=0;k<=2*fix;++k)
                    if(dp[j][k]>=0)
                        if(dp[j][k]+add[i]>dp[j+1][k+sub[i]]){
                            dp[j+1][k+sub[i]]=dp[j][k]+add[i];
                            path[j+1][k+sub[i]]=path[j][k];
                            path[j+1][k+sub[i]].push_back(i);
                        }
        int kk;
        for(kk=0;kk<=fix;++kk)
            if(dp[m][fix+kk]>=0||dp[m][fix-kk]>=0)
                break;
        S=dp[m][fix+kk]>dp[m][fix-kk]?fix+kk:fix-kk;
        A=dp[m][S];
        P=(A+(S-fix))/2,D=(A-(S-fix))/2;
        printf("Jury #%d
",cas++);
        printf("Best jury has value %d for prosecution and value %d for defence:
",P,D);
        for(int i=0;i<m;++i)
            printf(" %d",path[m][S][i]);
        printf("

");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/ke-xin/p/13501320.html