目录
1 01背包
1) 采药
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N];//滚动数组优化后的
int main()
{
int n,m;
cin>>m>>n;
for(int i=0;i<n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--) f[j]=max(f[j],f[j-v]+w);//从打到小滚动,要么选当前数,要么不选
}
cout<<f[m]<<endl;
return 0;
}
2) 装箱问题
只不过问剩余体积,那就v也看出w来做,最后V-价值最大就行了
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int f[N];
int main()
{
int n,m;
cin>>m>>n;
for(int i=0;i<n;i++)
{
int v;
cin>>v;
for(int j=m;j>=v;j--) f[j]=max(f[j],f[j-v]+v);
}
cout<<m-f[m]<<endl;
return 0;
}
3)开心的金明
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=3e4+10;
int f[N];
int main()
{
int n,m;
cin>>m>>n;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--)
f[j]=max(f[j],f[j-v]+v*w);
}
cout<<f[m];
return 0;
}
2 二维背包
1) 二维费用的背包问题
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int f[N][N];
int main()
{
int n,b,c;
scanf("%d%d%d",&n,&b,&c);
for(int i=1;i<=n;i++) {
int v,m,w;
scanf("%d%d%d",&v,&m,&w);
for(int j=b;j>=v;j--)//优化一维
for(int k=c;k>=m;k--)
f[j][k]=max(f[j][k],f[j-v][k-m]+w);//要么选第i个,要么不选第i个
}
printf("%d",f[b][c]);
return 0;
}
2)宠物小精灵之收服
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int K=1e3+10,M=510;
int f[K][M];//滚动数组优化
int main()
{
int V1,V2,n;
cin>>V1>>V2>>n;
for(int i=1;i<=n;i++)
{
int v1,v2;
cin>>v1>>v2;
//滚动数组从大到小滚,这样就会用到i-1的值了,不会用到第i层的值
for(int j=V1;j>=v1;j--)//状态计算
for(int k=V2-1;k>=v2;k--)
f[j][k]=max(f[j][k],f[j-v1][k-v2]+1);//更新最大值,要么选这个精灵,要么不选
}
cout<<f[V1][V2-1]<<' ';//输出最大可以收服的数量
int k=V2-1;
while(k>0&&f[V1][k-1]==f[V1][V2-1]) k--;//假如在V1的条件下,求最大的不相等的值
cout<<V2-k<<endl;//输出剩余的体力
return 0;
}
3)潜水员
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=22,M=80;
int f[N][M];
int main()
{
int V1,V2,n;
cin>>V1>>V2>>n;
memset(f,0x3f,sizeof f);
f[0][0]=0;//表示前0个物品选第一个体积V1至少为0的,第二个体积V2至少为0的价值为0,而其他不存在初始化为正无穷,表示更新最小值不用到他
for(int i=1;i<=n;i++)
{
int v1,v2,w;
cin>>v1>>v2>>w;
for(int j=V1;j>=0;j--)
for(int k=V2;k>=0;k--)
f[j][k]=min(f[j][k],f[max(0,j-v1)][max(0,k-v2)]+w);//假如小于0,就让他等于0
}
cout<<f[V1][V2]<<endl;//输出至少为V1和V2的最小价值
return 0;
}
3 多重背包
1)多重背包3(单调队列优化)
(5条消息) 17 堆排序与模拟堆_钊气蓬勃.的博客-CSDN博客
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int f[N],q[N],g[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
int v,w,s;
scanf("%d%d%d",&v,&w,&s);
memcpy(g,f,sizeof f);//g是f上一层的数组,也就是滚动过来的数组
for(int j=0;j<v;j++)
{
int hh=0,tt=-1;
for(int k=j;k<=m;k+=v)//单调队列优化来求
{
if(hh<=tt&&q[hh]<k-s*v) hh++;//判断队头是否已经滑出窗口
if(hh<=tt) f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w);//更新一下最大值
while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w) tt--;//假如队尾的数大于当前数,则出队
q[++tt]=k;//把当前数入队
}
}
}
cout<<f[m]<<endl;
return 0;
}
2)庆功会
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int f[N];
int main()
{
int n,m;
cin>>n>>m;
//多重背包1
for(int i=1;i<=n;i++)
{
int v,w,s;
cin>>v>>w>>s;
for(int j=m;j>=v;j--)
for(int k=0;k<=s&&k*v<=j;k++)
f[j]=max(f[j],f[j-k*v]+k*w);
}
cout<<f[m];
return 0;
}
4完全背包
1)买书(求方案数)
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int f[N],v[4]={10,20,50,100};//把书的费用看成体积
int main()
{
int n;
cin>>n;
f[0]=1;//前0本书,价格为0的有一种方案,其他不合法,方案数为0,因为没有书得不到其他价格
for(int i=0;i<4;i++)
{
for(int j=v[i];j<=n;j++)
f[j]+=f[j-v[i]];
}
cout<<f[n]<<endl;
return 0;
}
5分组背包
1)金明的预算方案
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
把每个主件看成一组,把组件与附件的结合方式看成改组的k个
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> pii;
const int N=32010;
pii zhu[N];//用来存主件
vector<pii> fu[N];//用来附件主件
int f[N];
int main()
{
int n,m;
cin>>m>>n;
for(int i=1;i<=n;i++)
{
int v,w,q;
cin>>v>>w>>q;
if(!q) zhu[i]={v,v*w};//把主件放进去
else fu[q].push_back({v,v*w});//把附件存该主件下的附件
}
for(int i=1;i<=n;i++)
if(zhu[i].x)//假如是主件
for(int j=m;j>=0;j--)
{
int g=fu[i].size();//表示该主件附件的数
for(int k=0;k<1<<g;k++)//二进制枚举在附件有g个的情况下的选法
{
int v=zhu[i].x,w=zhu[i].y;
for(int u=0;u<g;u++)//枚举位数,判断该位数是否为1,为1则选该附件
if(k>>u&1) v+=fu[i][u].x,w+=fu[i][u].y;
if(j>=v) f[j]=max(f[j],f[j-v]+w);//假如合法,则转移
}
}
cout<<f[m];
return 0;
}
5背包问题求方案数
1)数字组合(01背包求方案数)
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int f[N];
int main()
{
int n,m;
cin>>n>>m;
f[0]=1;//表示前0个物品选恰好等于0的方案数为1,而其他从前0个选恰好为1,2,3的这种情况不可能有,所以其他是0
for(int i=0;i<n;i++)
{
int v;
cin>>v;
for(int j=m;j>=v;j--)
f[j]+=f[j-v];
}
cout<<f[m];
return 0;
}
2)货币系统(完全背包求方案数)
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
long long f[N];
int main()
{
int n,m;
cin>>n>>m;
f[0]=1;//因为前0个物品恰好为0的也是一种情况
for(int i=1;i<=n;i++)//完全背包
{
int v;
cin>>v;
for(int j=v;j<=m;j++)
f[j]+=f[j-v];//求方案数
}
cout<<f[m];
return 0;
}
3)货币系统NOIP提高组(贪心+完全背包求方案数)
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
将a数组进行排序,看后面的数能否被前面的数表示,假如能说明这个数多余了,假如不能则这个数必不可缺
#include<bits/stdc++.h>
using namespace std;
const int N=25010,M=110;
int f[N],a[M];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,cnt=0;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);//将a从小到大排序
memset(f,0,sizeof f);//把上一次的状态清空
f[0]=1;//前0个数能表示0也是一种方案
int m=a[n-1];//最大要表示的数,也即最大体积
for(int i=0;i<n;i++)//多重背包求方案数
{
int v=a[i];//v是当前数
if(!f[v]) cnt++;//假如这个数不能被表示,也即方案数为0的时候,说明这个数必不可缺
for(int j=v;j<=m;j++) f[j]+=f[j-v];//求表示每个数的方案数
}
cout<<cnt<<endl;
}
return 0;
}
4)背包问题求方案数(01背包,类似最短路)
#include<bits/stdc++.h>
using namespace std;
const int N=1010,mod=1e9+7;
int f[N],g[N];//f的状态转移方程为体积恰好为j的最大价值
int main()
{
int n,m;
cin>>n>>m;
memset(f,-0x3f,sizeof f);//由于是恰好为j,则初始化为正无穷
f[0]=0;//前0个体积恰好为0的最大价值为0
g[0]=1;//前0个体积恰好为0有一个方案数
for(int i=0;i<n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--)
{
int maxv=max(f[j],f[j-v]+w),cnt=0;//用maxv记录哪边的最大值,cnt记录该体积下的方案数
if(maxv==f[j]) cnt+=g[j];//假如最大价值是f[i-1][j],则方案数加上g[i-1][j]
if(maxv==f[j-v]+w) cnt+=g[j-v];//假如最大价值是f[i-1][j-v],则方案数加上g[i-1][j-v]
g[j]=cnt%mod;//该体积下的最大价值的方案数
f[j]=maxv;//更新最大价值
}
}
//由于是恰好体积为j,所以不知道最大价值对应的体积是哪个
int res=0;
for(int i=0;i<=m;i++) res=max(res,f[i]);
//求一下最大价值对应的方案数
int ans=0;
for(int i=0;i<=m;i++)
if(res==f[i])
ans=(ans+g[i])%mod;
cout<<ans<<endl;
return 0;
}
6背包问题求方案
1)背包问题求具体方案数(01背包求方案)
求字典序最小就求状态计算的时候从大到小求,求方案就从小到大求
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int f[N][N],v[N],w[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
//从后往前更新选的数
for(int i=n;i;i--)
for(int j=0;j<=V;j++)
{
f[i][j]=f[i+1][j];//这步不能少,表示不选第i个数的时候,待会输出方案时要用到
if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
}
//从前往后输出选的数,也就是选最大时的方案
for(int i=1,j=V;i<=n;i++)
if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i]) cout<<i<<' ',j-=v[i];
return 0;
}
2)机器分配 (分组背包求方案)
求字典序最小就求状态计算的时候从大到小求,求方案就从小到大求
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
1.把每个公司看成一组
2.每个公司里需要的台数看成分组背包的k个,每个对应的盈利看成价值
3.按照最大盈利输出方案
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int f[N][N],w[N][N],way[N];//用w存第i组物品的各物品数的分别价值,way存的是每组物品分别用到那个
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>w[i][j];
//把v看成一,因为每次给一台
for(int i=n;i;i--)
for(int j=0;j<=m;j++)
for(int k=0;k<=j;k++)
f[i][j]=max(f[i][j],f[i+1][j-k]+w[i][k]);
cout<<f[1][m]<<endl;//输出最大价值
//求方案
for(int i=1,j=m;i<=n;i++)
for(int k=0;k<=j;k++)
if(f[i][j]==f[i+1][j-k]+w[i][k])//求方案跟上一题的思路一样
{
way[i]=k;
j-=k;
break;
}
for(int i=1;i<=n;i++) cout<<i<<' '<<way[i]<<endl;//输出每个公司需要的台数
return 0;
}
7 其他组合
1)混合背包(01+完全+多重背包)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i<n;i++)
{
int v,w,s;
cin>>v>>w>>s;
if(s==0)//完全背包求最大价值
for(int j=v;j<=V;j++)
f[j]=max(f[j],f[j-v]+w);
else//多重背包用二进制优化成01背包做法
{
if(s==-1) s=1;//假如是01背包,则s的数量为1
for(int k=1;k<=s;s-=k,k*=2)//二进制优化多重背包
for(int j=V;j>=k*v;j--)//枚举每个数量下的最大价值
f[j]=max(f[j],f[j-k*v]+k*w);
if(s)//假如还有剩余,则在求一次最大值
for(int j=V;j>=s*v;j--) f[j]=max(f[j],f[j-s*v]+s*w);
}
}
cout<<f[V]<<endl;
return 0;
}
2)有依赖的背包问题(树形DP+分组背包)
把每个根的子树看成分组背包的该组个数,
可以把子根里的元素由体积划分,假如由个数划分则2的k次方那么大,会超时,所以每个子根由体积来划分
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int v[N],w[N];
int h[N],e[N],ne[N],idx;//领接表来存树
int f[N][N];
int n,m;
void add(int a,int b)//把a连到b的后面
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
for(int i=h[u];i!=-1;i=ne[i])//枚举物品组,也就是子根
{
int son=e[i];
dfs(son);//先求子根的最大值才用来更新自己的
//分组背包求最大价值
for(int j=m-v[u];j>=0;j--)//循环体积,因为自己占v[u]的体积,则求的时候得小于等于m-v[u]
for(int k=0;k<=j;k++)//循环决策,子根的体积就是决策,按体积划分
f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
}
//把物品u的体积下的价值存进去,用来更新他的父节点
for(int i=m;i>=v[u];i--) f[u][i]=f[u][i-v[u]]+w[u];
for(int i=0;i<v[u];i++) f[u][i]=0;//假如体积加上父节点的体积大于最大容量了,则价值不存在,也就是为0
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);//初始化头结点为-1
int root;
for(int i=0;i<n;i++)
{
int v,w,p;
cin>>v>>w>>p;
if(p==-1) root=i;
else add(p,i);
}
dfs(root);
cout<<f[root][m]<<endl;//输出以这个为根的最大价值
return 0;
}
3)能量石(贪心+01背包)
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int n;
struct Stone//定义一个结构体来存能量石的三个数
{
int s,e,l;
bool operator<(const Stone &W) const//重载小于号,也就是按照推出来的公式进行排序
{
return s*W.l<l*W.s;
}
}stone[N];
int f[N];
int main()
{
int t;
cin>>t;
for(int C=1;C<=t;C++)
{
int m=0;//用来存能量石总能量
cin>>n;
for(int i=0;i<n;i++)
{
int s,e,l;
cin>>s>>e>>l;
stone[i]={s,e,l};
m+=s;
}
sort(stone,stone+n);//按照贪心的排序
memset(f,0,sizeof f);//清空上一状态
for(int i=0;i<n;i++)
{
int s=stone[i].s,e=stone[i].e,l=stone[i].l;
for(int j=m;j>=s;j--)//01背包求最大值
f[j]=max(f[j],f[j-s]+e-(j-s)*l);
}
int ans=0;
for(int i=0;i<=m;i++) ans=max(ans,f[i]);//求一次最大能量值
printf("Case #%d: %d\n",C,ans);
}
return 0;
}