背包

01背包
有一个容量为\(V\)的背包和\(N\)种物品。知道每种物品的价值\(w_{i}\)和体积\(v_{i}\)(一种物品只有一个),求在背包能装下的情况下能拿走的物品的最大总价值。
思路:
\(f_{ij}\)表示前\(i\)种中能拿走的物品总体积为\(j\)时能拿走的物品的最大总价值。对于第\(i\)种物品,有选或不选两种情况,选了,前\(i-1\)种物品中能拿走的物品总体积为\(j-v_{i}\)就是\(f_{ij}\)的前一个状态,即\(f_{ij}=f_{i-1j-v_{i}}+w_{i}\),不选,就是从\(f_{i-1j}\)直接转移过来,即\(f_{ij}=f_{i-1j}\),两个式子合并后就是\(f_{ij}=max\left(f_{i-1j-v_{i}},f_{i-1j}\right)\)
代码:

#include<iostream>
using namespace std;
int N,V;
int v[110],w[110];
int f[110][1010];
int ans=0;
int main(){
	cin>>V>>N;
	for(int i=1;i<=N;i++){
		cin>>v[i]>>w[i];
	}
	for(int i=1;i<=N;i++){
		for(int j=0;j<=V;j++){
			if(j>=v[i])f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			else f[i][j]=f[i-1][j];
			ans=max(ans,f[i][j]);
		}
	}
	cout<<ans;
	return 0;
} 

完全背包
这个和01背包只有一点不同,每种物品有无穷多个。
思路:
这里每种物品可以不拿,拿一个,拿两个,……只要背包能装下,即\(f_{ij}= \underset{0<=k<=j/v_{i}}{max} f_{i-1j-k*v_{i}}+k*w{i}\)
代码:

#include<iostream>
using namespace std;
int N,V;
int v[110],w[110];
int f[110][1010];
int ans=0;
int main(){
	cin>>N>>V;
	for(int i=1;i<=N;i++){
		cin>>v[i]>>w[i];
	}
	for(int i=1;i<=N;i++){
		for(int j=0;j<=V;j++){
			for(int k=0;k<=j/v[i];k++){	
				f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
			}
			ans=max(ans,f[i][j]);
		}
	}
	cout<<ans;
	return 0;
} 

但是这样的时间复杂度是\(O(n^{3})\),很慢,既然每件物品能拿无穷多个,也就是说无论什么情况下(背包不能装了除外),可以拿任何一种物品,\(f_{j}\)表示用\(j\)的空间能拿走的物品价值总和最大值,即\(f_{j}=max(f_{j},f_{j-v_{i}}+w_{i})\)
代码:

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

多重背包
也是只与01背包有一处不同,就是每种物品有\(s_{i}\)个。
思路:
只需要在完全背包的\(O(n^{3})\)做法上加一个限制\(k<=s[i]\),即\(f_{ij}= \underset{0<=k<=min(j/v_{i},s[i])}{max} f_{i-1j-k*v_{i}}+k*w{i}\)
代码

#include<iostream>
using namespace std;
int n,v;
int u[110],w[110],s[110];
int f[110][110];
int ans=0;
int main(){
    cin>>n>>v;
    for(int i=1;i<=n;i++){
        cin>>u[i]>>w[i]>>s[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=v;j>=0;j--){
            for(int k=0;k<=min(s[i],j/u[i]);k++){
                f[i][j]=max(f[i][j],f[i-1][j-k*u[i]]+k*w[i]);
            }
            ans=max(ans,f[i][j]);
        }
    }
    cout<<ans;
    return 0;
}

但是\(O(n^{3})\)还是慢,这里可以用二进制优化,是将每种物品进行分组,第一组1个,第二组2个,第三组4个,以此类推,直到不满一组,则剩下的全部归为一组。
这样可以组合成所有情况,从第一组开始,可以满足\([0,1]\)中的所有情况,第二组满足\([2,3]\)中的所有情况,加上前面就是可以满足\([0,3]\)中所有情况,以此类推,到最后一组就可以满足\([0,s_{i}]\)中的所有情况。每组只有一个,这就是01背包问题。
代码:

#include<iostream>
using namespace std;
int n,v;
int u[1010],w[1010],s[1010];
int wet[12010],val[12010],t[12010];
int f[2010];
int cnt=0;
int main(){
	cin>>n>>v;
	for(int i=1;i<=n;i++){
		cin>>u[i]>>w[i]>>s[i]; 
	}
	for(int i=1;i<=n;i++){
		int k=1;
		while(k<s[i]){
			t[++cnt]=k;
			s[i]-=k;
			wet[cnt]=t[cnt]*u[i];
			val[cnt]=t[cnt]*w[i];
			k*=2;
		}
		t[++cnt]=s[i];
		wet[cnt]=t[cnt]*u[i];
		val[cnt]=t[cnt]*w[i];
	}
	for(int i=1;i<=cnt;i++){
		for(int j=v;j>=0;j--){
			if(j>=wet[i])f[j]=max(f[j],f[j-wet[i]]+val[i]);
		}
	}
	int ans=0;
	for(int i=0;i<=v;i++){
		ans=max(ans,f[i]);
	}
	cout<<ans;
	return 0;
}

分组背包
就是在01背包的基础上,将物品进行了分组,且规定一组中只能选择一件物品。
思路:
也是在01背包上进行轻微改动,\(f_{ij}\)中的\(i\)表示前\(i\)组,其余照01背包做就可以了。
代码:

#include<iostream>
using namespace std;
int n,v;
int mo[110],cnt;
int u[10010],w[10010];
int f[110][110];
int ans=0;
int main(){
    cin>>n>>v;
    cnt=1;
    for(int i=1;i<=n;i++){
        cin>>mo[i];
        mo[i]=mo[i-1]+mo[i];
        for(;cnt<=mo[i];cnt++){
            cin>>u[cnt]>>w[cnt];
        }
    }
    cnt=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=v;j++){
            f[i][j]=f[i-1][j];
        }
        for(;cnt<=mo[i];cnt++){
            for(int j=0;j<=v;j++){
                if(j>=u[cnt])f[i][j]=max(f[i][j],f[i-1][j-u[cnt]]+w[cnt]);
            }
        }
        for(int j=0;j<=v;j++){
            ans=max(ans,f[i][j]);
        }
    }
    cout<<ans;
    return 0;
}
posted @ 2022-05-09 21:29  zzzzzz2  阅读(74)  评论(0)    收藏  举报