ybtoj「动态规划」第1章 背包问题

A. 【例题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;
}

B. 【例题2】货币系统

这题看起来就很不可做的样子()

实际上这题就是求原来的货币有多少能被自己表示出来

可以用完全背包的思想:先将货币从小到大排序

\(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;
}

C. 【例题3】宝物筛选

朴素做法:把每一类物品拆成 \(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;
}

D. 【例题4】硬币方案

思路类似刚才的货币系统 只多了一个 \(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;
}

E. 【例题5】金明的预算方案

有依赖背包 实际上不难

因为一个主件最多两个附件 所以最多只有 \(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;
}

J. 5.计数问题

写了详细题解 大家可以来看看link

posted @ 2023-10-25 09:39  xiang_xiang  阅读(27)  评论(0)    收藏  举报