背包问题求方案数
此处需要注意,虽然是01背包的另一种问法,但此题需要把dp[1…V]初始化为负无穷。这样做的原因是,如果dp数组全部初始化为0,那么dp[k]的含义为容量为k时背包装的最大价值(注意此处不一定被装满了),那么这样就会造成重复计数的问题。若把dp[1…m]初始化为负无穷,那么最大价值一定是从0开始转移的,也就是说若dp[k]是最大价值,那么一定装满了容量k。
初始化负无穷,可以得到从0转移过来的,关键是Max = 0。所有数组肯定还是有更新的,之所以能把从0转移过来的和其他的分开,就是因为给 dp[0] 赋成0了,不是从0转移过来的存的数据都是负无穷,所以维护最大值Max时,从其他状态转移过来的就直接无视掉了。
对于有限制的选择问题,比如恰好选
k
k
k 个,就可以这样初始化
code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
const int mod = 1e9 + 7;
int dp[N];
int num[N];
int Max = 0;
int main()
{
int n, V, M,w, v, m, s;
//若dp[1-V]=0 则dp的含义为容量为k时能装的最大价值 不一定用完k容量
//因此把dp[1-V]=inf 这样处理后 dp[k]的含义为容量恰好为k时能装的最大价值
memset(dp, -0x3f, sizeof(dp));
num[0] = 1;
dp[0] = 0;
cin >> n >> V ;
for(int i = 1; i <= n; ++i)
{
cin >> v >> w;
for(int j = V; j >= v; --j)
{
if(dp[j] < dp[j - v] + w)
num[j] = num[j - v], dp[j] = dp[j-v] + w;
else if(dp[j] == dp[j - v] + w)num[j] = (num[j] + num[j - v]) % mod;
Max = max(dp[j], Max);
}
}
long long ans = 0;
for(int i = 0; i <= V; ++i)
{
if(dp[i] == Max) ans += num[i], ans %= mod;
}
cout << ans << endl;
return 0;
}
01背包求具体方案
因为要求字典序最小,那么我们肯定采取贪心策略(能选序号小的就选序号小的)
我们如果从前往后遍历所有的物品,那么最后 dp[n][m] 就是最后答案,那我们就得从后往前遍历才可以求的具体方案 ,但是这样所求的是字典序最大的
所以我们应该反一下,从后往前去遍历所有物品,这样dp[1][m]就是最后答案,那么我们就从前往后遍历就可以求具体方案,这样求的是字典序最小的
至于为什么要从最终答案倒退:因为真正答案是转移过程中产生的,我们并不知道何时产生的,产生答案的也不止一定只有一个,如果从开始遍历不确定终点。(可能是这样解释吧)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int n, m, v[N], w[N];
int dp[N][N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> v[i] >> w[i];
for(int i = n; i >= 1; --i)
{
for(int j = 0; j <= m; ++j)// 这层必须从0开始
{
dp[i][j] = dp[i+1][j];
if(j >= v[i])
dp[i][j] = max(dp[i][j], dp[i+1][j-v[i]] + w[i]);
}
}
int vol = m;
for(int i = 1; i <= n; ++i)
{
if(vol - v[i] >= 0 && dp[i][vol] == dp[i+1][vol-v[i]] + w[i])
{
cout << i << " ";
vol -= v[i];
}
}
return 0;
}
01背包
如果背包容量比较大,按之前的思路写会超时,该怎么办?
例题博客
思路:
普通的
01
01
01 背包是:容量为
j
j
j 时,装尽量大的价值(遍历的是容量范围
当背包容量很大的时候(一般给定的价值范围会比较小,这时候转换思维,价值为
j
j
j,确定尽量小的容量(遍历的是价值范围
要求容量最小,注意初始化最大,倒着找第一个满足容量范围的价值即可
Robberies
题意:
第一行给定 总被抓概率
p
p
p 和 银行数量
n
n
n
小偷去偷
n
n
n 个银行,总被抓概率
p
p
p (最后求得的总概率必须小于他,否则被抓),每个银行有一个可以偷到的价值
w
i
w_i
wi 和被抓住的概率
v
i
v_i
vi,求不被抓的情况下,最多偷到的物品总价值
思路:
首先概率是小数,而总价值是整数,并且范围小于等于
1
e
4
1e4
1e4,考虑物品价值作为背包容量,而直接用被抓的概率作为价值,这样是否合适?
假设我们偷了
k
k
k 个银行,那么我们没有被抓的概率,应该是
(
1
−
p
1
)
∗
(
1
−
p
2
)
∗
.
.
.
(
1
−
p
k
)
(1-p_1)*(1-p_2)*...(1-p_k)
(1−p1)∗(1−p2)∗...(1−pk)
因此应该用成功脱身的概率作为价值
这样背包就确定了,用物品价值去确定尽量大的脱身概率
注意初始化,最后我们求得的
f
[
i
]
f[i]
f[i] 代表的是偷总价值
i
i
i 的物品,脱身的概率为
f
[
i
]
f[i]
f[i]
那么被抓的概率是
(
1
−
f
[
i
]
)
(1-f[i])
(1−f[i]),倒着遍历找到满足
(
1
−
f
[
i
]
)
<
q
(1-f[i])<q
(1−f[i])<q 最大的
i
i
i 即可
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
ll n, m;
double f[maxn];
void work()
{
double m;
cin >> m >> n;
for(int i = 0; i <= 10000; ++i) f[i] = 0;
f[0] = 1;
for(int i = 1; i <= n; ++i){
ll w;double v;
cin >> w >> v;
for(int j = 10000; j >= w; --j)
f[j] = max(f[j], f[j - w] * (1 - v));
}
for(int i = 10000; i >= 0; --i) if((1 - f[i]) < m){
cout << i << endl;return;
}
}
int main()
{
ios::sync_with_stdio(0);
int TT;cin>>TT;while(TT--)
work();
return 0;
}
给容量添加限制条件
HDU3466-Proud Merchants
题意:
有
n
n
n 个商人卖货物,每个商人只卖一件货物,货物的价格为
p
i
p_i
pi,价值为
v
i
v_i
vi,但是这里的商人比较自负,如果你手头没有
q
i
q_i
qi 那么他会拒绝和你交易。有
m
m
m 的货币,问能获得的最大价值是多少。
在01背包的基础上,第
i
i
i 个物品只有在容量大于等于
q
i
q_i
qi 时才能选择
Sample Input
n
,
m
n,m
n,m
p
,
q
,
v
p,q,v
p,q,v
2 10
10 15 10
5 10 5
3 10
5 10 5
3 5 6
2 7 3
Sample Output
5
11
思路:
先放题解
由于容量限制条件的存在,会产生物品选择顺序不同,得到答案不同的情况,也就是不满足无后效性这个条件
p
p
p 价格,即体积,
q
q
q 容量限制,
v
v
v 价值
模拟一组测试数据:
2 10
5 10 5
3 5 6
1)若先选择第一组先来:
背包所用容量: 0 1 2 3 4 5 6 7 8 9 10
第一遍循环:: 0 0 0 0 0 0 0 0 0 0 5
第二遍循环:: 0 0 0 0 0 6 6 6 6 6 6
2)若先选择第二组先来:
背包所用容量: 0 1 2 3 4 5 6 7 8 9 10
第一遍循环:: 0 0 0 0 0 6 6 6 6 6 6
第二遍循环:: 0 0 0 0 0 6 6 6 6 6 11
可见选择物品的顺序会影响最后的结果。
再看方程: d p [ j ] = m a x ( d p [ j ] , d p [ j − a r r [ i ] . p ] + a r r [ i ] . v ) dp[j] = max(dp[j], dp[j - arr[i].p] + arr[i].v) dp[j]=max(dp[j],dp[j−arr[i].p]+arr[i].v)
结合测试数据发现,只有保证 d p [ j − a r r [ i ] . p ] dp[j - arr[i].p] dp[j−arr[i].p] 最优,才能保证 d p [ j ] dp[j] dp[j] 最优,满足无后效性。
若想使 d p [ j − a r r [ i ] . p ] dp[j - arr[i].p] dp[j−arr[i].p] 最优,即要保证:
对于任意两组值: p 1 , q 1 , v 1 和 p 2 , q 2 , v 2 p_1, q_1, v_1 \ 和 \ p_2, q_2, v_2 p1,q1,v1 和 p2,q2,v2
考虑贪心算法,邻项交换法
假设先选择第一组,则若想满足无后效性,
那么 j − a r r [ 2 ] . p > = a r r [ 1 ] . q j-arr[2].p >= arr[1].q j−arr[2].p>=arr[1].q ,且 j − a r r [ 1 ] . p < = a r r [ 2 ] . q j-arr[1].p <= arr[2].q j−arr[1].p<=arr[2].q (否则可能出现,依赖先选 2 2 2 )计算的值取到更优的解)
由此推得: a r r [ 1 ] . q − a r r [ 1 ] . p < = a r r [ 2 ] . q − a r r [ 2 ] . p arr[1].q - arr[1].p <= arr[2].q - arr[2].p arr[1].q−arr[1].p<=arr[2].q−arr[2].p,得到如何去决定物品的顺序
当然如果不想这样证明,还有另外一种理解方式:
当
q
<
=
p
q<=p
q<=p 的时候,这个物品无论何时选择,容量限制不会影响最优解,而
q
>
p
q>p
q>p 时,容量限制才会对最优解产生影响,并且
q
−
p
q-p
q−p 越大,对最优解产生影响的可能越大,所以我们排序时让
q
−
p
q-p
q−p 小的在前
q
−
p
q-p
q−p 的差值越小,在每次循环时,与基本的
01
01
01 背包相比,不更新的值也就越少,也就越接近基本的01背包,求出的值也就相对越大。
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int f[maxn];
struct node
{
int p, q, v;
bool operator<(const node &B)const{
return (q - p) < (B.q - B.p);
}
}a[maxn];
void work()
{
//cin >> n >> m;
for(int i = 0; i <= m; ++i) f[i] = 0;
for(int i = 1; i <= n; ++i)
{
cin >> a[i].p >> a[i].q >> a[i].v;
}
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
for(int j = m; j >= max(a[i].p, a[i].q); --j)
f[j] = max(f[j], f[j - a[i].p] + a[i].v);
cout << f[m] << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
while(cin >> n >> m)
work();
return 0;
}
01背包变形:两个条件最优化
UVA12563 劲歌金曲 Jin Ge Jin Qu hao----洛谷链接
vj
题意:
KTV里面有
n
n
n 首歌曲你可以选择,每首歌曲的时长都给出了. 对于每首歌曲,你最多只能唱
1
1
1 遍. 现在给你一个时间限制
t
(
t
<
=
1
0
9
)
t \ (t<=10^9)
t (t<=109) , 问你在最多
t
−
1
t-1
t−1 秒的时间内可以唱多少首歌曲
n
u
m
num
num , 且最长唱歌时间是多少
t
i
m
e
(
t
i
m
e
必
须
<
=
t
−
1
)
time (time必须<=t-1)
time(time必须<=t−1) ? 最终输出
n
u
m
+
1
num+1
num+1 和
t
i
m
e
+
678
time+678
time+678 即可.
注意: 你需要优先让歌曲数目最大的情况下,再去选择总时长最长的.
思路:
01背包板子题
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int t[55], Max, _ = 0;
struct node
{
int num, time;
bool operator<(const node &B)const{
if(num != B.num) return num < B.num;
else return time < B.time;
}
}f[maxn];
void work()
{
cout << "Case " << ++_ << ": ";
cin >> n >> Max;
memset(f, 0, sizeof(f));
int sum = 0;//所有歌曲总时长
for(int i = 1; i <= n; ++i)
cin >> t[i], sum += t[i];
Max = min(sum, Max - 1);
int ans = 0;
for(int i = 1; i <= n; ++i)
{
for(int j = Max; j >= t[i]; --j)
{
node tmp = {f[j - t[i]].num + 1, f[j - t[i]].time + t[i]};
f[j] = max(f[j], tmp);
}
}
cout << f[Max].num + 1 << " " << f[Max].time + 678 << endl;
}
int main()
{
ios::sync_with_stdio(0);
int TT;cin>>TT;while(TT--)
work();
return 0;
}