1、背包问题--竞赛真理
每个物品有多种采用方式
http://www.rqnoj.cn/problem/160
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
//每样物品有两种选择方法
//value[1] pri[1] value[2] pri[2]
int value[3][40],pri[3][40];
int f[1080002];
int n,t;
int main(){
scanf("%d %d",&n,&t);
for(int i=1;i<=n;i++){
scanf("%d %d %d %d",&value[1][i],&pri[1][i],&value[2][i],&pri[2][i]);
}
for(int i=1;i<=n;i++){
for(int j=t;j>0;j--){
for(int k=1;k<=2;k++){
if(j>pri[k][i]){
f[j]=max(f[j],f[j-pri[k][i]]+value[k][i]);
}
}
}
}
printf("%d\n",f[t]);
return 0;
}
2、分组背包:金明的预算方案

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
//有约束的背包问题
//在考虑主件的时候一起考虑附件
//当有1个附件的时候,有两种方案
//有2个附件的时候,有4中方案
int num[100];//有多少个东西
int f[100][100][3]; //第二维的时候数字(表示策略的种数),第三维 1 表示重要度*价值, 2 表示代价
int value[100],imp[100],q[100];
int n,m;
int dp[32001];
int type[100] ; //这个标识选取方法数
void pre(){ //预处理策略
for(int i=1;i<=m;i++){
if(num[i]==1){
type[i]=1; //只有主件
}
else if(num[i]==2){
//有一个附件
type[i]=2;
}
if(num[i]>=2){
//先处理2+主件
f[i][2][1]+=f[i][1][1];
f[i][2][2]+=f[i][1][2];
}
if(num[i]==3){
type[i]=4; //有两个附件
f[i][3][1]+=f[i][1][1];f[i][3][2]+=f[i][1][2]; //3+主件
f[i][4][1]=f[i][2][1]+f[i][3][1]-f[i][1][1]; //两个附件都带上
f[i][4][2]=f[i][2][2]+f[i][3][2]-f[i][1][2];
}
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d %d",&value[i],&imp[i],&q[i]);
if(!q[i]){
num[i]++;
f[i][1][1]=value[i]*imp[i];
f[i][1][2]=value[i];
}
else{
num[q[i]]++;
f[q[i]][num[q[i]]][1]=value[i]*imp[i];
f[q[i]][num[q[i]]][2]=value[i];
}
}
pre();
for(int i=1;i<=m;i++){
if(num[i]>=0){
for(int j=n;j>=0;j--){
for(int k=1;k<=type[i];k++){ //策略数
if(f[i][k][2]<=j){
dp[j]=max(dp[j],dp[j-f[i][k][2]]+f[i][k][1]);
}
}
}
}
}
printf("%d\n",dp[n]);
return 0;
}
复习
重点理解!!如果进行状态转移的!
struct node{
int v,w;
}bag[maxn];
int n,v;
int dp[maxn][maxn];
int dp(){
memset(dp,0,sizeof(dp));
//二维:就是顺序
//改为一维的dp就是必须逆序(使用滚动数组)
for(int i=1;i<=n;i++){
for(int j=0;j<v;j++){
if(bag[i].v>j) dp[i][j]=d[i-1][j];
else dp[i][j]=max(dp[i-1][j],dp[i-1][j-bag[i].v]+bag[i].w);
}
}
return dp[n][v];
}
- 01背包:每个东西只有一份
cin>>m>>n;
for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){ //逆序
f[j]=max(f[j-w[i]]+c[i],f[j]); //这里是J
}
}
cout<<f[m]<<endl;
- 完全背包:每个东西可以取无限份
cin>>m>>n;
for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
for(int i=1;i<=n;i++){//顺序
for(int j=w[i];j<=m;j++) if(f[j]<f[j-w[i]]+c[i]) f[j]=f[j-w[i]]+c[i];
}
cout<<"max="<<f[m]<<endl;
- 混合背包:有的物品有多个有的物品只有1个,分别进行处理,逆序or正序,注意选择多个的时候循环次序,物品数量在外层,背包重量在内层
cin>>m>>n;
for(int i=1;i<=n;i++) cin>>w[i]>>c[i]>>s[i];
for(int i=1;i<=n;i++){
if(s[i]==0){ //完全背包,顺序
for(int j=w[i];j<=m;j++){
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
else {//01背包和多重背包,逆序
for(int j=1;j<=s[i];j++) //注意顺序,先是物品个数,再是剩余重量
for(int k=m;k>=w[i];k--)
f[k]=max(f[k],f[k-w[i]]+c[i]); //这里没有j噢
}
}
cout<<f[m]<<endl;
- 多重背包,可以通过二进制处理降低复杂度,原理是每个数都可以表示位2进制相加的形式,所以这样就把它处理为了01背包问题
-
int x,y,n1=0,s,t; for(int i=1;i<=n;i++){ cin>>x>>y>>s; t=1; while(s>=t){ w[++n1]=t*x; //重量 c[n1]=t*y; //价值 s-=t; t*=2; } w[++n1]=s*x; c[n1]=s*y; } //接下来就是01背包问题 for(int i=1;i<=n1;i++){ //注意数量 for(int j=m;j>=w[i];j--){ f[j]=max(f[j],f[j-w[i]]+c[i]); } } cout<<f[m]<<endl;
一道用单调队列优化多重背包的做法
1601:【例 5】Banknotes
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=210;
const int N=2e4+10;
const int INF=0x3fffffff;
typedef long long LL;
int n,m;
int val[maxn],num[maxn];
LL dp[N];
//单调队列优化多重背包)
/*
单调队列优化:对于模Bi相同的几个权值之间的dp转移,可以用单调队列优化,令权值V=j+k*Bi,dp[V]=min(dp[V],dp[j+k'*Bi]+k-k‘),
所以可以用dp[j+k*Bi]-k最小为队首的单调队列来优化成n*m,(细节:为了防止被反复统计,应该先插入当前节点再更新当前节点的dp值)
*/
//还是有点难理解
struct node{
int k; //当前的个数
int num; //dp[j+k*Bi]-k当前的值
};
node q[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&val[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
}
scanf("%d",&m);
//一般来说多重背包的写法,但是这样会超时
/*
for(int i=1;i<=k;i++) dp[i]=INF;
dp[0]=0;
for(int i=1;i<=n;i++){
for(int j=k;j>=val[i];j--){
for(int z=1;z<=min(num[i],j/val[i]);z++){
dp[j]=min(dp[j],dp[j-z*val[i]]+z);
}
}
}
*/
/*
for(int i=1;i<=n;i++){
for(int j=1;j<=num[i];j++){
for(int z=k;z>=val[i];z--){
dp[z]=min(dp[z],dp[z-val[i]]+1);
}
}
}
*/
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
int x;
for(int i=1;i<=n;i++){//枚举第i种硬币
for(int d=0;d<val[i];d++){ //枚举除以该硬币面额的余数
int L=1,R=0; //维护一个队首元素(num) 最小的队列
for(int j=0;;j++){ // x=b[i]*j + d ,枚举j
x=j*val[i]+d; //此时的总金额
if(x>m) break;
while(L<=R&&j-q[L].k>num[i]) L++; // //判断是否超过c[i]
//个数-队首的个数
while(L<=R&&dp[x]-j<=q[R].num) R--; //判断是否满足单调
q[++R]=node{j,dp[x]-j}; //更新前入队
if(q[L].num+j<dp[x]) dp[x]=q[L].num+j; //更新
}
}
}
printf("%lld\n",dp[m]);
return 0;
}
- 二维背包问题:eg.潜水员
cin>>m>>n>>k;
memset(f,127,sizeof(f));//赋值为一个很大的数,因为是要求最小的
f[0][0] =0; //f[0][0]要初始化
for(int i=1;i<=k;i++) cin>>mm[i]>>nn[i]>>w[i];
for(int i=1;i<=k;i++){
for(int j=m;j>=0;j--){//都是01背包 费用1
for(int k=n;k>=0;k--){ //费用2
int t1=j+mm[i];
int t2=k+nn[i]; //注意写法 ,不能在外面直接判断
if(t1>m) t1=m;
if(t2>n) t2=n;
if(f[t1][t2]>f[j][k]+w[i]) f[t1][t2]=f[j][k]+w[i];
}
}
}
cout<<f[m][n]<<endl;
- 分组背包,背包分组,注意循环的顺序,最外层是组数,中间层是剩余的体积,最里层是这个组里面的物品序号
int p=0;
cin>>v>>n>>k;
for(int i=1;i<=n;i++){
cin>>w[i]>>c[i]>>p;
g[p][++g[p][0]]=i; //这种写法,合并了两个功能
}
for(int i=1;i<=k;i++){ //分的组数
for(int j=v;j>=0;j--){ //第二层是体积
for(int k=1;k<=g[i][0];k++){ //这个组的物品序号
if(j>=w[g[i][k]]){ //注意是>=,不然结果不正确
int temp=g[i][k];
if(f[j]<f[j-w[temp]]+c[temp]) f[j]=f[j-w[temp]]+c[temp];
}
}
}
}
cout<<f[v]<<endl;
posted on
浙公网安备 33010602011771号