Knapsack背包问题入门

Knapsack背包问题入门

  • 0/1背包问题[O( C )]
  • 0/1背包的回溯问题
  • 0/1背包问题概率变种:
  • 多重背包问题[O(C* Σ \Sigma Σn)]
  • 完全背包问题

0/1背包问题[O( C )]

已知:
背包容量 C,
物品种类 N,每种物品有且仅有1个
每种物品拥有负载 w 和价格 p 两个属性。
设(N+1)×2 维数组{ vector> w_p },
w_p[n].first ------------ 物品n的负载
w_p[n].second ------- 物品n的价格

问题:
背包可装物品的最大价值。

求解:
设(N+1)×(C+1)维数组并初始化为0,
V[n][c]表示 容量为 c 的背包装载前 n 个物品的最大价值
当装载第n个物品时应考虑:
是否可以装入:
w_p[n] > c
若不可以,则装载前n个物品的价值 = 装载前n-1个物品的价值
V[n][c] = V[n-1][c]
若可以,则选择 装载前n个物品的价值 与 装载前 n-1 物品的价值 中的较大值
V[n][c] = max( V[n-1][c - w_p[n].first] + w_p[n].second, V[n-1][c] )

二维数组动态规划

for(int i=1;i<=N;i++){
   for(int j=1;j<=C;j++){
   	if(j < w_p[i].first)
   		V[i][j] = V[i-1][j];
   	else{
   		V[i][j] = max(V[i-1][j], V[i-1][j-w_p[i].first]+w_p[i].second);
   	}
   }
}
// V[N][C]即为背包容量为C时的最大价值

一维数组动态规划

for(int i=1;i<=N;i++){
	for(int j=C;j>=1;j--){
		if(j >= w_p[i].first){
			V[j] = max(V[j], V[j-w_p[i].first]+w_p[i].second);
		}
	}
}
// V[C]即为背包容量为C时的最大价值

递归方法
需实现对(N+1)×2 维数组{ vector> w_p }
按单位负载的价格{second/first}进行升序排列

int getans(int now_num,int now_weight,int now_value,int limit){
    if(now_num>(int)arr.size())
        return 0;
    if(now_value+(double)arr[now_num].second/arr[now_num].first*(limit-now_weight)<max_value)   //剪枝
        return 0;
    if(now_value>max_value)
        max_value=now_value;
    if(now_weight+arr[now_num].first<=limit)
        getans(now_num+1,now_weight+arr[now_num].first,now_value+arr[now_num].second,limit);
    getans(now_num+1,now_weight,now_value,limit);
    return max_value;
}

getans(0,0,0,C);

0/1背包的回溯问题

已知:
背包容量 C,
物品种类 N,每种物品有且仅有1个
每种物品拥有负载 w 和价格 p 两个属性。
设(N+1)×2 维数组{ vector> w_p },
w_p[n].first ------------ 物品n的负载
w_p[n].second ------- 物品n的价格
以及背包容量为 C 时的最大价值V

问题:
求实现该最大价值的组合

求解:
已知二维动态规划所求出的 N×C 的 V 矩阵
从V[N][C]开始:
若V[n][c] == V[n-1][c]----则第n个不在最大价值组合中
否则----则第n个在最大价值组合中,再从V[n-1][c-w_p[n].first]开始

0/1背包问题概率变种:

已知:
N个项目,资金预算C
每个项目能仅只能投一次
每个项目拥有投入成本 w 和成功概率 p 两个属性。
设(N+1)×2 维数组{ vector> w_p },
w_p[n].first ------------ 项目n的投入成本
w_p[n].second ------- 项目n的成功概率
问题:
项目的至少成功一个的概率
等价于:
1 - 所有项目一个都不成功的概率

求解:
设(N+1)×(C+1)维数组并初始化为0,
V[n][c]表示 资金预算为 c, 投资前 n 个项目的至少成功一个的概率
当投资第n个项目时应考虑:
预算是否够用:
w_p[n] > c
若不够用,则投资前n个项目至少成功一个的概率 = 投资前n-1个项目至少成功一个的概率
V[n][c] = V[n-1][c]
若够用,则选择 投资前n个项目至少成功一个的概率 与 投资前 n-1 项目至少成功一个的概率 中的较大值

for(int i=1;i<=N;i++){
	for(int j=1;j<=C;j++){
		if(j < w_p[i].first)
			V[i][j] = V[i-1][j];
		else{
			V[i][j] = max(V[i-1][j], 
			1-(1-V[i-1][j-w_p[i].first])*(1-w_p[i].second));
		}
	}
}
// V[N][C]即为资金预算为C时项目至少成功一个的概率

附:过程中记录,不需要表格查询

int N,C; cin>>N>>C;
dp[0] = 0;
for(int i=1;i<=N;i++){
	int w; cin>>w; a[i] = w;
	for(int j=C;j>=w;--j){
		if(j-w > 0){
			dp[j] += dp[j-w];
			last[j] = j-w;	//保留各节点余量
			}
	}
}
int load = C;
while(load != 0){
	int x = last[load];
	vis[load - x] = 1;	//通过余量还原所选项
	load = x;
}
for(int i=1;i<=N;i++){
	if(!vis[a[i]]){
		cout<<i;
	}
}

多重背包问题[O(C* Σ \Sigma Σn)]

每种物品k个的背包问题

int N, C;	cin>>N>>C;
for(int i=1;i<=N;i++){
	int w, p, n; cin>>w>>p>>n;
	for(int j=C;j>=w;j--){
		for(int k=1;k<=n && j>=k*w;k++){
			V[j] = max(V[j], V[j-k*n]+k*p);
		}
	}
}
// V[C]即为背包容量为C时的最大价值

第一种优化思路
[O(C* Σ \Sigma Σlogn)]

int N, C;	cin>>N>>C;
vector<pair<int, int>> w_p;
// 记录拆分后的数目
int num = 0;
for(int i=1;i<=N;i++){
	int w, p, n; cin>>w>>p>>n;
	// 按二进制拆分数量为n的第i个物品为cnt个
	int cnt = int(log(n)/log(2)) + 1;
	num = num + cnt;
	for(int j=0;j<cnt;j++){
		//n取2对数不为整的边缘拆分
		if(n/pow(2, j)==1 && n%pow(2, j)!=0){
			pair<int,int> tmp1(w*(n%pow(2, j)+1),0);
			w_p.push_back(tmp1);
			w_p[j].second = p*(n%pow(2, j)+1);
		}
		//n取2对数为整的边缘拆分
		else if(n/pow(2, j)==1 && 
				 n%pow(2, j)==0){
			num = num - 1;
		}
		//n的普通拆分
		else if(n/pow(2,j)>1){
			pair<int,int> tmp2(w*pow(2, j),0);
			w_p.push_back(tmp2);
			w_p[j].second = p*pow(2, j);
		}
	}
}
for(int i=0;i<num;i++){
	for(int j=C;j>=w_p[i].first;j--){
		V[j] = max(V[j], V[j-w_p[i].first]+w_p[i].second);
	}
}
// V[C]即为背包容量为C时的最大价值

第二种优化思路
[O(C*N)]

单调队列方法

完全背包问题

每种物品不限的背包问题

(无界的完全背包问题)[O(C* Σ \Sigma Σn)]

int N, C;	cin>>N>>C;
for(int i=1;i<=N;i++){
	int w, p, n;	cin>>w>>p>>n;
	for(int j=C;j>=w;j--){
		for(int k=1;j>=k*w;k++){
			V[j] = max(V[j], V[j-k*w]+k*p);
		}
	}
}
// V[C]即为背包容量为C时的最大价值

优化思路(逐一等效)

V[i][j] = V[i-1][j]
		= V[i-1][j-w] + p
		= V[i-1][j-2w] + 2p
		...
		==V[i-1][j-kw] + kp
//左端变化得:
V[i][j-w] = V[i-1][j-w]
		  = V[i-1][j-2w] + p
		  = V[i-1][j-3w] + 2p
		  ...
		  = V[i-1][j-kw] + (k-1)p
//优化得:
V[i][j] = max(V[i-1][j], V[i][j-w]+p);

从小到大枚举背包大小可以保证负载 j 从小的转移时已被更新过,从本身转移的时候还是 i-1 的状态值。

优化结果[O(C*N)]

int N, C;	cin>>N>>C;
for(int i=1;i<=N;i++){
	int w, p;	cin>>w>>p;
	for(int j=w;j<=C;j++){
		V[j] = max(V[j], V[j-w]+p);
	}
}
// V[C]即为背包容量为C时的最大价值

你可能感兴趣的:(Knapsack背包问题入门)