【动态规划】背包问题

216 阅读3分钟

DP分析法

image.png

01背包

背包中的所有物品都只有一个
背包体积V 物品数N 每个物品的价值Wi 体积Vi

  • 集合:只从前i个物品里选,且总体积不大于j的所有选法
  • 集合划分

image.png

代码

var readline = require('readline');
rl = readline.createInterface({
    input:process.stdin,
    output:process.stdout
})
var items=[];
let n=-1;//数据行数 物品数
let m=-1;//背包总体积
rl.on('line',function(data){
    //第一行数据为 n,m 其余是物品数据
    if(n===-1){
        let firstRow = data.split(" ");
        n=parseInt(firstRow[0]);
        m=parseInt(firstRow[1]);
    }else{
        items.push(data);
    }
    
    if(items.length === n){
        //数据全部处理完,开始写逻辑
        const v = new Int32Array(n+1);
        const w = new Int32Array(n+1);
        for(let i =1;i<=n;i++){
            let item = items[i-1].split(" ");
            v[i]=parseInt(item[0]);
            w[i]=parseInt(item[1]);
        }
        let res = zeroOneBag(n,m,v,w);
        console.log(res);
    }
})

function zeroOneBag(n,m,v,w){
    const f = [];
    for(let i=0;i<=n;i++){
        f[i]=new Int32Array(m+1);
    }
    for(let i=1;i<=n;i++){
        for(let j=0;j<=m;j++){
            f[i][j]=f[i-1][j]
            if(v[i]<=j){
                f[i][j]=Math.max(f[i][j],f[i-1][j-v[i]]+w[i]);
            }
        }
    }
    return f[n][m];
}

优化

f[i][j] = Max(f[i-1][j],f[i-1][j-v[i]]+w[i])
只与第i-1层有关,那么就可以利用滚动数组压缩为一维

function zeroOneBag(n,m,v,w){
    const f = new Int32Array(m+1);
    for(let i=1;i<=n;i++){
        for(let j=m;j>=0;j--){
            if(v[i]<=j){
                f[j]=Math.max(f[j],f[j-v[i]]+w[i]);
            }
        }
    }
    return f[m];
}
//要保证 f[j]是第i-1层算过的 而f[j-v[i]]+w[i]是第i层算的 j就要从大到小遍历

完全背包

背包中所有物品都有无数个
背包体积V 物品数N 每个物品的价值Wi 体积Vi

  • 集合 只选前i种物品且总体积小于j的所有选法
  • 集合的划分

image.png

朴素的做法就是比01背包多加一重循环,遍历第i种物品的数量0-k
但是三重循环时间复杂度太高了

for(let i=1;i<=n;i++){
        for(let j=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            for(let k=0;k*v[i]<=j;k++){
                f[i][j]=Math.max(f[i][j],f[i-1][j-k*v[i]]+w[i])
            }
        }
    }

优化

f[i][j] = Max( f[i-1][j],f[i-1][j-v]+w],f[i-1][j-2v]+2w,...,f[i-1][j-kv]+kw) ①

f[i][j-v]=Max(f[i-1][j-v],f[i-1][j-2v]+w ,f[i-1][j-3v]+2w,...,f[i-1][j-kv]+(k-1)w) ②

①②错位相减=>f[i][j] = Max( f[i-1][j], f[i][j-v]+w )

=> 只跟第i层、第i-1层有关 =>一维数组

function fullBag(n,m,v,w){
    const f= new Int32Array(m+1);
    for(let i=1;i<=n;i++){
        for(let j=0;j<=m;j++){
            if(v[i]<=j){
                f[j]=Math.max(f[j],f[j-v[i]]+w[i]);
            }
        }
    }
    return f[m];
}

多重背包

背包中第i种物品有s[i]个
背包体积V 物品数N 每个物品的价值Wi 体积Vi

  • 集合的划分 image.png 思路和完全背包的朴素做法差不多,就是k值增加了一个上限s[i]

代码

function multiBag(n,m,v,w,s){
    const f=[];
    for(let i=0;i<=n;i++){
        f[i]=new Int32Array(m+1);
    }
    for(let i=1;i<=n;i++){
        for(let j=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            for(let k=0;k<=s[i]&&k*v[i]<=j;k++){
                f[i][j]=Math.max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    return f[n][m];
}

优化:二进制优化

二进制思想: n=100可以由2^i(i=0-n)组合表示:1+2+4+8+16+32...(n减完之前的数之后不足 2^i,取最后剩余的数),相当于把十进制的数用二进制来表示。那么要判断的数据就从100降低到100的二进制的位数。

对于多重背包问题来说,就从一种多个物品,转化成多种单个物品,就可以用0-1背包的方法解。

var readline = require('readline');
rl=readline.createInterface({
    input:process.stdin,
    output:process.stdout
});
let inputs = [];
let n = -1;
let m = -1;

rl.on('line',function(data){
    if(n===-1){
        const line = data.split(" ");
        n=parseInt(line[0]);
        m=parseInt(line[1]);
        
    }else{
        inputs.push(data)
    }
    if(inputs.length === n){
        const v=[];
        const w=[];
        let cnt=0;
        for(let i=1;i<=n;i++){
            const input = inputs[i-1].split(" ");
            let pv=parseInt(input[0]);
            let pw=parseInt(input[1]);
            let s=parseInt(input[2]);
            let k=1;
            while(k<=s){
                cnt++;
                v[cnt]=pv*k;
                w[cnt]=pw*k;
                s-=k;
                k*=2;
            }
            if(s>0){
                cnt++;
                v[cnt]=pv*s;
                w[cnt]=pw*s;
            }
        }
        const res = multiBag(cnt,m,v,w);
        console.log(res);
    }
})
function multiBag(n,m,v,w){//其实就是01背包了
    const f=new Int32Array(m+1);
    for(let i=1;i<=n;i++){
        for(let j=m;j>=v[i];j--){
            f[j]=Math.max(f[j],f[j-v[i]]+w[i]);
        }
    }
    return f[m];
}

分组背包

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j是组内编号。

  • 集合的划分

image.png

和多重背包问题比较相似,不过没有什么好的优化方法。

let fs = require('fs');
let buf = '';

process.stdin.on('readable', function() {
    let chunk = process.stdin.read();
    if (chunk) buf += chunk.toString();
});

process.stdin.on('end', function() {
    const lines=buf.split('\n').map(x=>x.split(" ").map(x=>parseInt(x)));
    const firstLine = lines.shift();
    const n=firstLine[0];
    const m=firstLine[1];
    let s=[];
    let v=[];
    let w=[];
    for(let i=1;i<=n;i++){
        s[i]=lines.shift()[0];
        v[i]=new Int32Array(s[i]+1);
        w[i]=new Int32Array(s[i]+1);
        for(let j=1;j<=s[i];j++){
            let item = lines.shift();
            v[i][j]=item[0];
            w[i][j]=item[1];
        }
    }
   const res = groupBag(n,m,s,v,w);
   console.log(res);
});

function groupBag(n,m,s,v,w){
    let f=[];
    for(let i=0;i<=n;i++){
        f[i]=new Int32Array(m+1);
    }
    for(let i=1;i<=n;i++){
        for(let j=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            for(let k=1;k<=s[i];k++){
                if(v[i][k]<=j){
                    f[i][j]=Math.max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
                }
            }
        }
    }
    return f[n][m];
}