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;
}

完成!

posted @ 2026-01-18 09:50  MichaelZeng  阅读(18)  评论(0)    收藏  举报