acwing动态规划(一)——背包问题

背包问题

核心思想
在这里插入图片描述

(1)01背包问题(物品只有1个)

二维数组

状态转移f[i][j] = max(f[i - 1][j] , f[i - 1][j - w[i]] + v[i])

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N][N];
int w[N] , v[N];
int  n , m; 
int main()
{
    int n , m;
    cin >> n >> m;
    
    for(int i = 1 ; i <= n ; i ++) cin >> w[i] >> v[i];
    
    for(int i = 1 ; i <= n ; i ++)
        for(int j = 0 ; j <= m ; j++)
        {
            f[i][j] = f[i - 1][j];//继承上一层i的价值
            if(j >= w[i]) f[i][j] = max(f[i][j] , f[i - 1][j - w[i]] + v[i]);//当背包可以装下的时候,取较大值
        }
        
    cout << f[n][m] << endl;
    return 0;
}

一维数组+滚动数组

最终版

#include <iostream>
#include <algorithm>

using namespace std;

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

int main()
{
    int n , m;
    cin >> n >> m;
    
    for(int i = 1 ; i <= n ; i ++) cin >> w[i] >> v[i];
    
    for(int i = 1 ; i <= n ; i ++)
        for(int j = m ; j >= w[i] ; j--) //因为每层i只用到i-1层的数据而用不到i-2层的,
        								 //因此可以利用滚动数组,此时应倒着遍历j
            f[j] = max(f[j] , f[j - w[i]] + v[i]);
        
        
    cout << f[m] << endl;
    return 0;
}

(2)完全背包(物品有无数个)

转自y总(转自y总)

暴力算法
状态转移f[i][j] = max(f[i][j] , f[i - 1][j - k * w[i]] + k * v[i])

#include <iostream>
#include <algorithm>

using namespace std;

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

int main(){
   
    cin >> n >>m;
    
    for(int i = 1 ; i <= n ; i++) cin >> w[i] >> v[i];
    
    for(int i = 1 ; i <= n ; i++)
        for(int j = 0 ; j <= m ; j++)
            for(int k = 0 ; k * w[i] <= j ; k++)//暴力判断每个物品的件数
                f[i][j] = max(f[i][j] , f[i - 1][j - k * w[i]] + k * v[i]); 
                														
    cout <<f[m] << endl;
    return 0;
}

时间优化:省去k循环

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

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

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];

    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= m; j++) 
        {
            f[i][j] = f[i - 1][j];
            if (j >= w[i]) f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);//因为物品数量无限,所以不用i-1层,只用更新
            															 //当前i层的价值即可
        }

    cout << f[n][m] << endl;

    return 0;
}

进一步优化:空间优化,较少一个维度
最终版

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

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

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];

    for (int i = 1; i <= n; i++)
        for (int j = w[i] ; j <= m; j++) 
            f[j] = max(f[j], f[j - w[i]] + v[i]); //原理类似01背包,但是完全背包需要正序遍历j,因为物品件数无限,
            									  //需要对刚刚更新过的数据继续更新,而非利用i-1层的数据
    cout << f[m] << endl;
    return 0;
}

下面是01背包的最终版,可以发现,区别仅在j的循环次序,而01背包和完全背包区别仅在1和∞。所以仅此区别也没什么奇怪
01背包的物品只有1个,因此需要逆序遍历j,这样就可以取到i-1层的f[j],不会重复使用当前的物品。
完全背包的物品有无限个,因此正序遍历时,取到的是当前i层的f[j],即已经被当前物品更新过的价值。

在这里插入图片描述

(3)多重背包(此时物品的个数不一定)

暴力算法O(nms)
状态转移f[i,j] = max(f[i - 1 , j - k * w]+k * v) (这里没有写错 , 遍历每种物品k次,取最大,k是物品的个数)
枚举每种物品的件数

#include <iostream>

using namespace std;

const int N = 110;
int f[N][N];
int  n , m;
int w[N] , v[N] , s[N];

int main()
{
    cin >> n >> m;
    
    for(int i = 1 ; i <= n ; i++) cin >> w[i] >> v[i] >> s[i];
    
    for(int i = 1 ; i <= n ; i++)
        for(int j = 0 ; j <= m ; j++)
            for(int k = 0 ; k <= s[i] && j >= k * w[i] ; k++)
         
                f[i][j] = max(f[i][j] , f[i - 1][j - k * w[i]] + k * v[i]);
            
        cout << f[n][m] << endl;
    
    return 0;
}
--------------------------------------------------------------------------------------------
#include <iostream>//同样可以空间优化,不过时间复杂度相同

using namespace std;

const int N = 110;
int f[N];
int  n , m;
int w[N] , v[N] , s[N];

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

二进制优化O(nmlogs )
算法核心:将一个s拆分成如1,、2、4、8…2k、c(其中c<2k+1,且该数列的和是s),进而问题转换成一个01背包问题

#include <iostream>

using namespace std;

const int N = 25000;

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

int main()
{
    cin >> n >> m;
    
    while(n--)
    {
        int a , b , s;
        cin >> a >> b >> s;
        
        int k = 1 ;
        while(k <= s)//将s转换成一个除最后一项外的以2位公比的等比数列
        {
            w[++cnt] = k * a;
            v[cnt] = k * b;
            s -= k;
            k *= 2;
        }
        
        if(s)//对最后一项c特判
        {
            w[++cnt] = s * a;
            v[cnt] = s * b;
        }
    }
    
    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]);
        
    cout << f[m] << endl;
    return 0;
}

(4)分组背包(一组物品只能取一个)

思路类似,只是每组中只取一个,遍历即可
状态转移f[j] = max(f[j] , f[j - w[i][k]] + v[i][k]);

#include <iostream>

using namespace std;

const int N = 110;

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

int main()
{
    cin >> n >> m;
    
    for(int i = 1 ;i <= n ; i++)
    {
        cin >> s[i];
        for(int j = 1 ; j <= s[i] ; j++)
            cin >> w[i][j] >> v[i][j];
    }
    
    for(int i = 1 ; i <= n ; i++)
        for(int j = m ; j >= 0 ; j--)
            for(int k = 1 ; k <= s[i] ; k++)
              if(w[i][k] <= j) //因为物品的遍历在体积之下,所以得判断一下,而不能直接在j循环里进行限制
              f[j] = max(f[j] , f[j - w[i][k]] + v[i][k]);
    
    cout << f[m] << endl;
    return 0;
}
发布了13 篇原创文章 · 获赞 3 · 访问量 340

猜你喜欢

转载自blog.csdn.net/Stephen_Zhao0/article/details/104837337
今日推荐