2021-07-02 暑训开端——重见dp

Posted KaaaterinaX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021-07-02 暑训开端——重见dp相关的知识,希望对你有一定的参考价值。

一、背包问题全集

1、01背包
每个物品只有一个
代码有注释行,放上普通版与滚动数组优化版
普通版:
01背包问题

#define ll long long

using namespace std;
const int maxn=1e3+7;

int v[maxn];
int w[maxn];
int dp[maxn][maxn];//选到第几件物品,当前背包内装的物品体积总和,当前价值为
int main(){
    int N,V;
    read(N);//快读,代码省略了
    read(V);
    for(int i=1;i<=N;i++){
        read(v[i]);
        read(w[i]);
    }
    dp[0][0]=0;
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            if(j<v[i]){
                dp[i][j]=dp[i-1][j];
            }
            else{
                dp[i][j]=max(dp[i-1][j-v[i]]+w[i],dp[i-1][j]);
            }
            //cout<<dp[i][j]<<' ';
        }
        //cout<<endl;
    }
    int ans=0;
    for(int i=0;i<=V;i++){
        ans=max(ans,dp[N][i]);
    }
    cout<<ans<<endl;
    
}

滚动数组版:


#define ll long long

using namespace std;

const int maxn=1e3+7;

int v[maxn];
int w[maxn];
int last[maxn];//当前背包内装的物品总体积为,当前价值为
int now[maxn];
int main(){
    int N,V;
    read(N);
    read(V);
    for(int i=1;i<=N;i++){
        read(v[i]);
        read(w[i]);
    }
    last[0]=0;
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            if(j+v[i]>V){
                now[j]=last[j];
            }
            else{
                now[j]=max(last[j+v[i]]+w[i],last[j]);
            }
            last[j]=now[j];
            //cout<<now[j]<<' ';
        }
        //cout<<endl;
        
    }
    int ans=0;
    for(int i=0;i<=V;i++){
        ans=max(now[i],ans);
    }
    cout<<ans<<endl;
    
}

2、完全背包
每个物品有无限个
一般思路:(在我没学之前想到的,n^3写法)
因为每一种物品都有无限个,那么选到每种物品,可以有多种状态转移的办法。
可以考虑这样的状态转移:

 for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            //枚举第i个物品选几个
            for(int k=0;k*v[i]<=j;k++){
                dp[i][j]=max(dp[i-1][j-k*v[i]]+k*w[i],dp[i][j]);
            }
        }
    }

但是由于复杂度过大,会TLE。

于是分析状态转移过程,上段代码的

dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]+2*v[i] ... );
dp[i][j-v]=max(.           dp[i-1][j-v[i],dp[i-1][j-2*v[i]]+w[i] ... );

可知,dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w[i])

这样以来,复杂度被压缩到n^2

using namespace std;

const int maxn=1e3+7;
int v[maxn];
int w[maxn];
int dp[maxn][maxn];//选到第几类物品,当前背包内已有的物品总体积,当前价值为
int main(){
    int N,V;
    read(N);
    read(V);
    for(int i=1;i<=N;i++){
        read(v[i]);
        read(w[i]);
    }
    for(int i=1;i<=N;i++){
        //枚举第i个物品选几个
        for(int j=0;j<=V;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=v[i]){
                dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
            }
        }
    }
    int ans=0;
    for(int i=0;i<=V;i++){
        ans=max(dp[N][i],ans);
    }
    cout<<ans<<endl;
}

这个也可以优化成滚动数组版。

3、多重背包
每个物品有有给定数目个
这类问题,一般思路可以想出一个n^3的写法,但是这种显然会在数据量稍稍上去一点就TLE。

这个时候可以用到一种特殊的优化方式——二进制优化。
设每种物品有S个。
假如S=100个,考虑将这个物品打包成1,2,4,…,32以及(100-63)个一组,每个包裹最多被选择一次。(2n+1-1 <= S)
运用数学归纳法可以证明存在一种选法,能凑出0~100中任意一个数。

所以,可以考虑将每一种物品打包成1,…,
2n ,
s-((2n+1)-1),转化为01背包问题。
多重背包问题 II
代码实现:


using namespace std;

const int maxn=2e4+5000;

int dp[maxn];

int v[maxn];
int w[maxn];
int s[maxn];

int nv[maxn];
int nw[maxn];

int main(){
    int N,V;
    cin>>N>>V;
    int cnt=0;
    for(int i=1;i<=N;i++){
        cin>>v[i]>>w[i]>>s[i];
        int k=1;
        while(k<=s[i]){
            cnt++;
            nv[cnt]=v[i]*k;
            nw[cnt]=w[i]*k;
            s[i]-=k;
            cout<<k<<endl;
            k*=2;
        }
        if(s[i]>0){
            cout<<s[i]<<endl;
            cnt++;
            nv[cnt]=v[i]*s[i];
            nw[cnt]=w[i]*s[i];
        }
    }
    //01背包
    for(int i=1;i<=cnt;i++){
        for(int j=V;j>=nv[i];j--){
            dp[j]=max(dp[j],dp[j-nv[i]]+nw[i]);
        }
    }
    cout<<dp[V]<<endl;
}

其实这个还可以继续优化,下面引入单调队列优化:

(咕咕咕)

4、分组背包
有多组物品,每组只能选x个

以上是关于2021-07-02 暑训开端——重见dp的主要内容,如果未能解决你的问题,请参考以下文章

暑训心得体会

[D. Johnny and Contribution] 思维 暑训第二天

添加两个窗格的平板电脑布局会导致在移动设备中找不到视图(小于w600dp)

1024. 视频拼接 dp

重见树状数组

重见树状数组