背包问题

 

 01背包问题:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int v[N], w[N];
int f[N][N];

int main()
{
    int n, m;
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= m; j++)
        {
            f[i][j] = f[i-1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
        }
    cout<<f[n][m]<<endl;
    return 0;
}

一维:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int v[N], w[N];
int f[N];

int main()
{
    int n, m;
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++) cin>>v[i]>>w[i];
    
    for(int i = 1; i <= n; i++)
        for(int j = m; j >= v[i]; j--)
        {
            f[j] = max(f[j], f[j-v[i]] + w[i]);
        }
    cout<<f[m]<<endl;
    return 0;
}

 转化为一维滚动数组的做法:

f[i][j]只和 i - 1的状态有关,所以可以只用第二维的状态来表示。

如果

j = 0; j <= m; j++

j 这样递减的话,那么f[j-v[i])肯定就在前面的f[j]当中就已经得出来了,重复了,然后这个f[j - v[i]]表示的其实是f[i][j-v[i]]的状态对应的是f[i][j](上一次的j等于此阶段的f[j - v[i]],因为j这个维度如果还是递增的话,那么f[j] = max(f[j], f[j-v[i]] + w[i]) 等价于 f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]; 事实上正确的应该是f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i])。所以要把j这个容量的维度循环改为递减。

int j = m; j >= v[i]; j--

这样f[j]不会覆盖,f[j-v[i]]就还没有被更新过,表示的也是下一个i - 1的阶段,即:f[i-1][j-v[i]]的值。

 

2 2 2 2 2 
6 6 6 4 
8 6 6 
8 6 
8

2、完全背包问题

 

 

                                     

 f[i][j] = f[i-k*v[i]] + k * w[i];

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int main()
{
    int n, m;
    cin>>n>>m;
    for(int i = 1; i <= n;i++) cin>>v[i]>>w[i];
    
    for(int i = 1;i <= n;i++)
        for(int j = 0; j <= m; j++)
            for(int k = 0; k * v[i] <= j; k++)
            {
                f[i][j] = max(f[i][j], f[i-1][j-k*v[i]] + k * w[i]);
            }
    cout<<f[n][m]<<endl;
    
    return 0;
}

f[i][j] = max(f[i - 1][j], f[i-1][j-v]+w, f[i-1][j-2v]+2w,f[i-1][j-3v]+3w,.......)

f[i,j-v] = max(      f[i-1][j-v],  f[i-1][j-2v] + w, f[i-1][j-3v] + 2w,.......)

完全背包:f[i,j] = max(f[i - 1, j], f[i][j - v[i] +w[i]]), 

01背包:f[i, j] = max(f[i-1, j], f[i-1][j-v[i]+w[i]);

把k的第三层循环去掉,

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int main()
{
    int n, m;
    cin>>n>>m;
    for(int i = 1; i <= n;i++) cin>>v[i]>>w[i];
    
    for(int i = 1;i <= n;i++)
        for(int j = 0; j <= m; j++){
                f[i][j] = f[i-1][j];
                if(j >= v[i]) 
                    f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
        }
    cout<<f[n][m]<<endl;
    
    return 0;
}

 同样把完全背包优化由二维优化为一维:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];
int main()
{
    int n, m;
    cin>>n>>m;
    for(int i = 1; i <= n;i++) cin>>v[i]>>w[i];
    
    for(int i = 1;i <= n;i++)
        for(int j = v[i]; j <= m; j++){
                    f[j] = max(f[j], f[j-v[i]] + w[i]);
        }
    cout<<f[m]<<endl;
    
    return 0;
}

这里就没有01背包的烦忧了,背包的容量m不需要递减遍历了。因为j是从小到大枚举的,然后f[j-v[j]]肯定在前面的f[j]就枚举出现过,然后第一维代表的肯定是i,这样就跟完全背包的状态计算完全一样:f[i][j] = max(f[i - 1][j], f[i][j-v[i]] + w[i]); 没有计算过的就是下一个层次的。

多重背包:

f[i][j] = max(f[i-1][j-v[i]*k] + k * w[i]);k =0,1,2,……,s[i]朴素版本的完全背包问题

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int v[N],w[N],f[N][N],s[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1;i <= n;i++) cin>>v[i]>>w[i]>>s[i];
    
    for(int i = 1; i <= n;i++)
        for(int j = 0; j <= m; j++)
            for(int k = 0; k <= s[i] && k * v[i] <= j; k++){
                f[i][j] = max(f[i][j], f[i - 1][j-k*v[i]] + k * w[i]);
            }
    cout<<f[n][m]<<endl;
}

 优化:先同上面的完全背包一样

f[i,j] = max(f[i-1,j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,...,f[i-1, j-sv]+sw);

f[i-1,j-v] = max(  f[i-1][j-v],  f[i-1][j-2v]+w,... f[i-1,j-sv]+(s-1)w), f[i-1, j - (s+1)v] + sw);

不能用前面的类似完全背包的优化方式

二进制优化方式:

 s = 200, 1, 2, 4, 8, 16, 32, 64,  + 73

0~127  73~200 这样就可以凑出0 ~ 200

 物品数量就可以从s降到logs,把物品的个数二进制分解,就不用从0枚举到s了。而是用logs个二进制数再加一个常数c就可以把数量s完全的表示出来。

原来的时间复杂度:N * V * S 变成对NlogS的物品做01背包 : N * V * logS = 1000 * 2000 * 12 = 2.4e7

多重背包的优化版本:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 24000, M = 2010;

int n, m;
int v[N], w[N];
int f[N];

int main()
{
    cin>>n>>m;
    
    int cnt = 0;
    for(int i = 1; i <= n;i++)
    {
        int a, b, s;
        cin>>a>>b>>s;
        int k = 1;
        while(k <= s)
        {
            cnt++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k <<= 1;
        }
        if(s > 0)
        {
            cnt++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    
    for(int i = 1; i <= n; i++)
        for(int j = m; j >= v[i]; j--)
        {
            f[j] = max(f[j], f[j-v[i]] + w[i]);
            
        }
    cout<<f[m]<<endl;
}

 分组背包问题:

有N组,每组有若干的物品,有对应的体积和价值,每组只能选其中一样的物品。

如果第i组物品不选的话,f[i - 1, j].

如果选了第 i 组第 k 个物品的话:f[i - 1, v[i, k]] + w[i, k].

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int v[N][N], w[N][N], s[N], f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i = 1;i <= n; i++)
    {
        cin>>s[i];
        for(int j = 0;j < s[i]; j++)
            cin>>v[i][j]>>w[i][j];
    }
    
    for(int i = 1; i <= n; i++)
        for(int j = m; j >= 0; j--)
            for(int k = 0; k < s[i]; k++)
                if(j >= v[i][k]) f[j] = max(f[j], f[j-v[i][k]] + w[i][k]);
    cout<<f[m]<<endl;
}

 

posted @ 2020-04-20 22:37  龙雪可可  阅读(119)  评论(0编辑  收藏  举报
****************************************** 页脚Html代码 ******************************************