acwing12
直接上题解,后面写有一般的对于方案没有限制的题解。若不懂如何求出最优解的具体方案,可以先看最下面的一般题解。
字典序最小的方案
状态表示:
d
p
[
i
]
[
j
]
表
示
第
i
个
物
品
到
第
n
个
物
品
体
积
为
j
时
的
最
优
解
dp[i][j]表示第i个物品到第n个物品体积为j时的最优解
dp[i][j]表示第i个物品到第n个物品体积为j时的最优解
因此状态转移方程变为:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
+
1
]
[
j
]
j
<
v
[
i
]
m
a
x
(
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
+
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
e
l
s
e
dp[i][j] = \begin{cases} dp[i + 1][j] & {j < v[i]}\\max(dp[i + 1][j], dp[i + 1][j - v[i]] + w[i]) & {else}\end{cases}
dp[i][j]={dp[i+1][j]max(dp[i+1][j],dp[i+1][j−v[i]]+w[i])j<v[i]else
思路:
因为可能有多个最优解,且要求字典序最小的最优解,所以要从第一个物品开始判断,且if的判断条件只能是j >= v[i] && dp[i][j] == dp[i + 1][j - v[i]] + w[i]
。
若采用dp[i][j] > dp[i + 1][j]
则会出错。
例如:
4 5
1 2
2 4
3 4
4 6
2 4 6 6 8
0 4 4 6 8
0 0 4 6 6
0 0 0 6 6
2 3(答案)
int dp[1003][1003];
int f[1003];
int w[N], v[N];
int main()
{
IOS;
int n, m; 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 = 1; j <= m; j++)
{
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]);
}
for (int j = m, i = 1; i <= n; i++)
{
//注意数组越界
if (j >= v[i] && dp[i][j] == dp[i + 1][j - v[i]] + w[i])
f[i] = 1, j -= v[i];
}
for (int i = 1; i <= n; i++)
if (f[i]) cout << i << " ";
return 0;
}
以下是一般的只需要求出具体方案的题解
思路
一般的求具体方案为正序对n个物品进行状态转移。
状态转移方程:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
]
j
<
v
[
i
]
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
e
l
s
e
dp[i][j] = \begin{cases} dp[i - 1][j] & {j < v[i]}\\max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) & {else}\end{cases}
dp[i][j]={dp[i−1][j]max(dp[i−1][j],dp[i−1][j−v[i]]+w[i])j<v[i]else
代码
if的判断条件可以是
d
p
[
i
]
[
j
]
>
d
p
[
i
−
1
]
[
j
]
dp[i][j] > dp[i - 1][j]
dp[i][j]>dp[i−1][j]也可以是
j
>
=
v
[
i
]
a
n
d
d
p
[
i
]
[
j
]
=
=
d
p
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
j >= v[i] ~~and~~ dp[i][j] == dp[i - 1][j - v[i]] + w[i]
j>=v[i] and dp[i][j]==dp[i−1][j−v[i]]+w[i]
两者的区别:
假如
d
p
[
n
]
[
m
]
=
d
p
[
n
−
1
]
[
m
]
>
d
p
[
n
−
2
]
[
m
]
dp[n][m] = dp[n - 1][m] > dp[n - 2][m]
dp[n][m]=dp[n−1][m]>dp[n−2][m]。由于是倒序循环的,前者会将
n
−
1
n - 1
n−1 标记为答案,而后者可能会将n标记为答案。
例如:(条件为前者时跑的样例)
4 5
1 2
2 4
3 4
4 6(样例)
2 2 2 2 2
2 4 6 6 6
2 4 6 6 8
2 4 6 6 8(dp数组)
2 3(方案)
(第二个if条件跑出来的方案则是1,4)
for (int j = m, i = n; i >= 1; i--)
{
if (dp[i][j] > dp[i - 1][j])
f[i] = 1, j -= v[i];
else f[i] = 0;
}
注意:使用第二个if条件虽然可以过上面的样例,但是求最优方案最小的字典序仍然时不对的。
一般求具体方案
int dp[1003][1003];
int f[1003];
int w[N], v[N];
int main()
{
IOS;
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
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]);
}
for (int j = m, i = n; i >= 1; i--)
{
if (j >= v[i] && dp[i][j] == dp[i - 1][j - v[i]] + w[i])
f[i] = 1, j -= v[i];
else f[i] = 0;
}
for (int i = 1; i <= n; i++)
if (f[i]) cout << i << " ";
return 0;
}