背包
背包
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\n",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\n",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\n",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\n",f[m]);
return 0;
}
单调队列优化 O(n*V)
https://www.cnblogs.com/-guz/p/9866118.html
蓝书P341 讲的很好
#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\n",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\n",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\n",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\n",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\n",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\n",cas++);
printf("Best jury has value %d for prosecution and value %d for defence:\n",P,D);
for(int i=0;i<m;++i)
printf(" %d",path[m][S][i]);
printf("\n\n");
}
return 0;
}