AT_abc441_f 题解
前言
赛时没想出来,赛后1h做出来了,但是方法好像不是最好。
题面
给定\(N\)个物品及每个的价格\(P_i\)和价值\(V_i\),每个物品有1个,以及高桥当前的钱数\(M\),我们设高桥最多能够得到的价值为\(S\)
对于所有的使总价值为\(S\)的购买方案,请问每个物品是:
- 如果总价值为\(S\)的所有购买方案都选了这个物品,那么这个物品为必选物品,为A类物品
- 如果总价值为\(S\)的所有购买方案中有些方案选了这个物品而有些方案没有,那么这个物品为可选物品,为B类物品
- 如果总价值为\(S\)的所有购买方案都没选这个物品,那么这个物品为不可选物品,为C类物品
输出一个长度为\(n\)的字符串,包含每个物品的种类。
\(N \leq 10^3\)
\(P_i \leq M \leq 5 \times 10^4\)
\(V_i \leq 10^9\)
解法
首先一眼看出这个是最朴素的01背包,本题复杂度最高为\(O(NM)\)
接下来要在做01背包的时候记下每个位置的答案从何而来:就是说\(dp_{i,j}\)是等于\(dp_{i-1,j}\)还是等于\(dp_{i-1,j-p_i}+v_i\)还是两个都等于。
再接下来,我们进行dfs枚举出所有的满足最高价值的方案。
在调用\(dfs(i,j)\)时
- 如果\(dp_{i,j} = dp_{i-1,j}\)且\(dp_{i,j} \neq dp_{i-1,j-p_i}+v_i\)
那么在这个方案中我们不选第\(i\)个物品,如果有别的方案选了(即这个物品被别的方案暂定为A类物品),那么这个物品是B类物品,否则这个物品暂定为C类物品,向\(dfs(i-1,j)\)进行递归
- 如果\(dp_{i,j} \neq dp_{i-1,j}\)且\(dp_{i,j} = dp_{i-1,j-p_i}+v_i\)
那么在这个方案中我们选第\(i\)个物品,如果有别的方案选了(即这个物品被别的方案暂定为C类物品),那么这个物品是B类物品,否则这个物品暂定为A类物品,向\(dfs(i-1,j-p_i)\)进行递归
- 如果\(dp_{i,j} = dp_{i-1,j}\)且\(dp_{i,j} = dp_{i-1,j-p_i}+v_i\)
那么要分两种情况:选/不选第\(i\)个都可以,进行分支枚举。向\(dfs(i-1,j-p_i)\), \(dfs(i-1,j)\)进行递归
如果在不同的方案中我们到达了相同的位置,后面一次可以跳过,否则相同位置后面的情况会重复算,不影响每个物品的选不选情况。
代码:
#include<bits/stdc++.h>
using namespace std;
int p[1001], v[1001];
int dp[1001][50001]; // 01背包dp
vector<pair<int, int> > fr[1001][50001]; // 找到从何而来
bool vis[1001][50001];
int k[1001];
void dfs(int i, int j)
{
for (pair<int, int> pii : fr[i][j])
{
int r = pii.first; // 找到这个来的地方
int c = pii.second;
if (c != j)
{
if (!k[i]) k[i] = 1; // 暂定为A
if (k[i] == 1) k[i] = 1; // A还是A
if (k[i] == 2) k[i] = 3; // 若被暂定为C,改成B(这里A=1,B=3,C=2)
}
else
{
if (!k[i]) k[i] = 2; // 暂定为C
if (k[i] == 1) k[i] = 3; // 若被暂定为A,改成B
if (k[i] == 2) k[i] = 2; // C还是C
}
if (!vis[r][c]) // 避免重复走
{
vis[r][c] = true;
dfs(r, c);
}
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> p[i] >> v[i];
for (int j = 0; j <= m; j++)
{
if (j < p[i] || dp[i - 1][j] > dp[i - 1][j - p[i]] + v[i]) // 记得先判断一下j<p[i]
{
fr[i][j].push_back({i - 1, j}); // 记下来的位置
dp[i][j] = dp[i - 1][j]; // 赋值
}
else
{
if (dp[i - 1][j] == dp[i - 1][j - p[i]] + v[i]) // 均可
{
fr[i][j].push_back({i - 1, j}); // 均记下来
fr[i][j].push_back({i - 1, j - p[i]});
dp[i][j] = dp[i - 1][j]; // 赋值,两个都可以
}
else
{
fr[i][j].push_back({i - 1, j - p[i]}); // 记下
dp[i][j] = dp[i - 1][j - p[i]] + v[i]; // 赋值
}
}
}
}
dfs(n, m); // 递归,从后往前
for (int i = 1; i <= n; i++)
{
if (k[i] == 1) cout << 'A'; // 注意输出,别输出错了
if (k[i] == 3) cout << 'B';
if (k[i] == 2) cout << 'C';
}
return 0;
}
于是喜提亿吨MLE和零星的WA
MLE的原因是不能把来的位置记下来,不然每个位置2个值,2个位置,还要乘上\(n \times m\),得到最高\(2 \times 2 \times 1000 \times 5 \times 10^4 = 2 \times 10^8\) 的需要的存储空间,明显不行,我们只记下一个数字(1,2,3中的一个),其中1表示不选,3表示均可,2表示必选
WA的原因是\(Long Long!\)
奉上AC代码!(只注释了和上面不同的地方)
#include<bits/stdc++.h>
using namespace std;
int p[1001], v[1001];
long long dp[1001][50001]; // long long
short fr[1001][50001]; // 改成一个数字,很小,就不用int了,可以用short
bool vis[1001][50001];
short k[1001];
void dfs(int i, int j)
{
for (int x = 0; x <= 1; x++) // 0代表不选,1代表选。
{
if (!(fr[i][j] & (1 << x))) // 可以手算一下,利用二进制就可以让1代表只能不选,2代表只能选,3代表均可。
{
continue;
}
int r = i - 1, c;
if (x == 0) c = j;
if (x == 1) c = j - p[i];
if (c != j)
{
if (!k[i]) k[i] = 1;
if (k[i] == 1) k[i] = 1;
if (k[i] == 2) k[i] = 3;
}
else
{
if (!k[i]) k[i] = 2;
if (k[i] == 1) k[i] = 3;
if (k[i] == 2) k[i] = 2;
}
if (!vis[r][c])
{
vis[r][c] = true;
dfs(r, c);
}
}
}
int main()
{
dp[0][0] = 0;
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> p[i] >> v[i];
for (int j = 0; j <= m; j++)
{
if (j < p[i] || dp[i - 1][j] > dp[i - 1][j - p[i]] + v[i])
{
fr[i][j] = 1; // 改成赋值成1个数字
dp[i][j] = dp[i - 1][j];
}
else
{
if (dp[i - 1][j] == dp[i - 1][j - p[i]] + v[i])
{
fr[i][j] = 3; // 同上
dp[i][j] = dp[i - 1][j];
}
else
{
fr[i][j] = 2; // 同上
dp[i][j] = dp[i - 1][j - p[i]] + v[i];
}
}
}
}
dfs(n, m);
for (int i = 1; i <= n; i++)
{
if (k[i] == 1) cout << 'A';
if (k[i] == 3) cout << 'B';
if (k[i] == 2) cout << 'C';
}
return 0;
}
当然还可以再优化一下代码的美观程度和长度(可以利用一下二进制)
高清:
#include<bits/stdc++.h>
using namespace std;
int p[1001], v[1001];
long long dp[1001][50001];
short fr[1001][50001];
bool vis[1001][50001];
int k[1001];
void dfs(int i, int j)
{
for (int x = 0; x <= 1; x++)
{
if (!(fr[i][j] & (1 << x)))
{
continue;
}
int r = i - 1, c;
if (x == 0) c = j;
if (x == 1) c = j - p[i];
if (c != j) k[i] |= 1;
else k[i] |= 2;
if (!vis[r][c])
{
vis[r][c] = true;
dfs(r, c);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> p[i] >> v[i];
for (int j = 0; j <= m; j++)
{
if (j < p[i] || dp[i - 1][j] >= dp[i - 1][j - p[i]] + v[i])
{
fr[i][j] |= 1;
dp[i][j] = dp[i - 1][j];
}
if (j >= p[i] && dp[i - 1][j] <= dp[i - 1][j - p[i]] + v[i])
{
fr[i][j] |= 2;
dp[i][j] = dp[i - 1][j - p[i]] + v[i];
}
}
}
dfs(n, m);
for (int i = 1; i <= n; i++)
{
if (k[i] == 1) cout << 'A';
if (k[i] == 3) cout << 'B';
if (k[i] == 2) cout << 'C';
}
return 0;
}
完成!

浙公网安备 33010602011771号