ybtoj「动态规划」第1章 背包问题
经典01背包
设 \(f_{i,j}\) 表示在 \(j\) 时间内挑前 \(i\) 株草药(不一定选不选)的最大价值
转移方程显然:\(f_{i,j}=max(f_{i-1,j},f_{i-1,j-w[i]}+v[i])\) (不选/选)
发现转移至于 \(i-1\) 有关 那么可以压掉第一维
这样需要倒序枚举 防止 \(f_j\) 被这一层的新状态覆盖
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=1e5+5;
int t,m,w[N],v[N],f[N],ans;
int main(){
scanf("%d%d",&t,&m);
for(int i=1;i<=m;i++)scanf("%d%d",&w[i],&v[i]);
for(int i=1;i<=m;i++){
for(int j=t;j>=w[i];j--){
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
printf("%d",f[t]);
return 0;
}
这题看起来就很不可做的样子()
实际上这题就是求原来的货币有多少能被自己表示出来
可以用完全背包的思想:先将货币从小到大排序
设 \(f_i\) 表示 \(i\) 能否用现有纸币表示出来
那么 \(f_i=f_i\ or\ (f_{a_j}\ and\ f_{i-a_j})\)
这里的 \(or\ and\) 分别是或和与
这里 \(i\) 正序枚举就是完全背包 (倒序就是01背包)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=1e5;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int t,n,m,a[N],f[N];
signed main(){
t=read();
while(t--){
n=read();m=0;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
if(!f[a[i]]){m++;f[a[i]]=1;}
for(int j=a[i];j<=a[n];j++)f[j]|=f[a[i]]&f[j-a[i]];
}
printf("%d\n",m);
}
return 0;
}
朴素做法:把每一类物品拆成 \(m\) 个 跑01背包
复杂度 \(O(W\sum m_i)\) 会T
考虑优化:对于一类物品 我们想办法让它分最少的组 还能让它凑出 \(\text{0~m}\) 所有组合
考虑二进制:发现每种组合都是几个二进制1合起来
那我们把 \(m\) 个拆成二进制每一位(1、2、4、8...) 剩下没分完的自成一组即可
这样就把所有物品分成了 \(\log\sum m_i\) 组 复杂度 \(O(W\log\sum m_i)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=1e5+5;
const int M=1e6+5;
inl int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'|c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,m,vi,wi,mi,v[N],w[N],cnt,f[N];
signed main() {
n=read();m=read();
for(int i=1;i<=n;i++){
vi=read(),wi=read(),mi=read();
for(int j=1;j<=mi;j<<=1){v[++cnt]=vi*j;w[cnt]=wi*j;mi-=j;}
if(mi){v[++cnt]=vi*mi;w[cnt]=wi*mi;}
}
for(int i=1;i<=cnt;i++)
for(int j=m;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+v[i]);
printf("%d\n",f[m]);
return 0;
}
思路类似刚才的货币系统 只多了一个 \(cnt\) 记录当前金额要拼成所需的最小硬币数
代码应该很好理解
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=1e5+5;
const int M=1e6+5;
inl int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'|c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int t,n,m,a[N],c[N],f[N],cnt[N],ans;
signed main() {
while(1){
n=read();m=read();ans=0;
if(!n&&!m)break;
memset(f,0,sizeof(f));f[0]=1;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=1;i<=n;i++){
memset(cnt,0,sizeof(cnt));
for(int j=a[i];j<=m;j++){
if(!f[j]&&f[j-a[i]]&&cnt[j-a[i]]<c[i]){f[j]=1;cnt[j]=cnt[j-a[i]]+1;}
if(f[j]&&f[j-a[i]]&&cnt[j-a[i]]+1<cnt[j])cnt[j]=cnt[j-a[i]]+1;
}
}
for(int i=1;i<=m;i++)ans+=f[i];
printf("%d\n",ans);
}
return 0;
}
有依赖背包 实际上不难
因为一个主件最多两个附件 所以最多只有 \(4\) 种组合(主\主+附1\主+附2\主+附1+附2)
分别转移即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=1e5;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int t,n,m,a[N],f[N],w[N][3],v[N][3],wi,vi,q,cnt[N],vis[N];
signed main(){
n=read();m=read();
for(int i=1;i<=m;i++){
wi=read();vi=read();q=read();
if(!q){
w[i][0]=wi;
v[i][0]=wi*vi;
vis[i]=1;
}else{
w[q][++cnt[q]]=wi;
v[q][cnt[q]]=wi*vi;
}
}
for(int i=1;i<=m;i++){
if(!vis[i])continue;
for(int j=n;j>=w[i][0];j--){
f[j]=max(f[j],f[j-w[i][0]]+v[i][0]);
if(!cnt[i])continue;
if(j>=w[i][0]+w[i][1])f[j]=max(f[j],f[j-w[i][0]-w[i][1]]+v[i][0]+v[i][1]);
if(cnt[i]^2)continue;
if(j>=w[i][0]+w[i][2])f[j]=max(f[j],f[j-w[i][0]-w[i][2]]+v[i][0]+v[i][2]);
if(j>=w[i][0]+w[i][1]+w[i][2])f[j]=max(f[j],f[j-w[i][0]-w[i][1]-w[i][2]]+v[i][0]+v[i][1]+v[i][2]);
}
}
printf("%d\n",f[n]);
return 0;
}
写了详细题解 大家可以来看看link

浙公网安备 33010602011771号