一、购物单
原题太多复制有乱码算了,贴个链接吧。
牛客
二、解题
看了题解博主思路和官方视频帮助理解。
我从学习动态规划是什么,到0-1背包问题,再到这个原题,花了两天半,流泪
就这写代码还是出错了两个地点,因为我始终没搞懂加了附件咋遍历的,反正理解不是很透彻
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HJ16_ShoppingList
{
class Program
{
/*
* 0-1-2-3背包问题
* 分析 1.价格是10的整数倍,读入时候可以除以10
* 2.先读取钱数与物品数量,将钱除10
* 3.将每个物品分为主,主+附件1,主+附件2,主+附件1+附件2的情况分别枚举
* 4.如果背包放得下,肯定东西越多价值越大,所以可依次枚举替换,
* 没有附件的再替换价值也是原值
* 5.价钱与价值都用二维数组存储
* 6.结果也用二维数组存储,行长度物品+1,列长度M+1
*/
public static void Main(string[] args)
{
string line;
while ((line = System.Console.ReadLine()) != null)
{
// 注意 while 处理多个 case
// 1.定义变量
string[] tokens = line.Split(' ');
int M = Convert.ToInt32(tokens[0]) / 10;// 总预算
int N = Convert.ToInt32(tokens[1]); // 物品数量
int[,] price = new int[N + 1, 3]; //价格数组
int[,] value = new int[N + 1, 3]; // 价值数组
int[,] dp = new int[N + 1, M + 1]; // 结果数组
// 输出测试
//System.Console.WriteLine(M + " " + N + "");
// 2.依次读入价格与重量
for (int i = 1; i <= N; i++)
{
// 物品编号从1开始
string line1 = System.Console.ReadLine();
string[] tokens1 = line1.Split(' ');
int a = Convert.ToInt32(tokens1[0]) / 10;// 物品价格
int b = Convert.ToInt32(tokens1[1]); // 重要程度
int c = Convert.ToInt32(tokens1[2]); //主件还是附件
if (c == 0)
{
// 物品是主件
price[i, 0] = a; //价格
value[i, 0] = b;// 重要度
}
else
{
if (price[c, 1] > 0)
{
// c号物品已经有附件1
price[c, 2] = a; //价格
value[c, 2] = b;// 重要度
}
else
{
// c号物品的附件1的价格和重要度
price[c, 1] = a;//价格
value[c, 1] = b;// 重要度
}
}
}
// 3.先输出试试看吧,新需求,怎么输出一大段并添加换行符
//Console.WriteLine("w[i][s]数组--i代表第几个物品,s代表其附件价格,没有附件则是0");
//for (int i = 0; i <= N; i++)
//{
// for (int j = 0; j < price.GetLength(1); j++)
// {
// if (price[i, j] == 0)
// {
// Console.Write(price[i, j] + "0");
// }
// else
// {
// Console.Write(price[i, j]);
// }
// Console.Write(" ");
// }
// Console.WriteLine();
//}
//Console.WriteLine("v[i][s]数组--i代表第几个物品,s代表其附件价值,没有附件则是0");
//for (int i = 0; i <= N; i++)
//{
// for (int j = 0; j < value.GetLength(1); j++)
// {
// if (value[i, j] == 0)
// {
// Console.Write(value[i, j] + "0");
// }
// else
// {
// Console.Write(value[i, j]);
// }
// Console.Write(" ");
// }
// Console.WriteLine();
//}
// 4.读取好了,开始处理吧
// 直接写状态转移函数与循环吧,算满意度时候记得枚举每个物品可能出现的情况
// 假设i代表第几个物品,k代表不同情况
// k=0:买一个主件;k=1:买一个主件和附件1;k=2:买一个主件和附件2;
// k=3:买一个主件,附件1,附件2
// 先按需求取出数据,一个主件的价格、价值,附件1的价格、价值
// 附件2的价格、价值
// 再按情况依次算
// 官方给的答案是四种情况分别赋值dp[i,j]
// ****假如均不超过容量,那么为什么主加附2一定可以替换主加附1?
// ****为什么不需要取这四个中满意度最大的?
// 记住满意度是价值*价格与状态转移方程
// 双循环i对物品数量,j对背包容量
for (int i = 1; i <= N; i++)
{
for (int j = i; j <= M; j++)
{
// ,b是主件的满意度,
// 满意度是主附件 价格*价值累加和,这里直接预处理
// 说明:price[i, 0]是主件的价格,price[i, 1]是附件1的价格,
// price[i, 2]是附件2的价格
// 假设a是只买主件的钱,b是只买主件的满意度
int a = price[i, 0], b = value[i, 0] * a;
// 假设c是买主+附1钱,d是买主+附1满意度
int c = a + price[i, 1], d = b + value[i, 1] * price[i, 1];
// 假设e是买主加附2钱,f是买主+附2满意度
int e = a + price[i, 2], f = b + value[i, 2] * price[i, 2];
// 假设g是买主+附1+附2钱,h是买主+附1+附2满意度
int g = a + price[i, 1] + price[i, 2], h = b + value[i, 1] * price[i, 1] + value[i, 2] * price[i, 2];
// 状态转移方程
// 官方答案三目运算符,我不熟悉还是先用我推出来的初级公式吧
/*
* 1.测试第二个用例时候错了,发现当主件加附件超过预算时候,返回上一级
* 是返回同行只买主件情况,不是很懂,需要数学模型
* 2.提交发现三组没对,再看大佬过程发现,
* 凡是加了主件需要返回的,都要回到只买主件
* 我还奇怪能买下咋不回到主件,原来是我当时只看了第一行,
* 第一行就是主件肯定返回上一级,然后下一行有了附件才是回到主件
*/
// a情况,只买主件
if (a > j)
{
// 物品价格大于预算,返回上一个物品dp[i-1,j],即表格中同列上一行
dp[i, j] = dp[i - 1, j];
}
else
{
// 物品价格小于预算,可以选择买或不买,分别求满意度取最大
// 买,满意度为买了a的满意度,加上,回到上一个物品再减去a的钱能买的最大满意度
int A = b + dp[i - 1, j - a];
// 不买,返回上个物品最大满意度
int B = dp[i - 1, j];
dp[i, j] = max(A, B);
}
// c情况,主件+附件1
if (c > j)
{
// 物品价格大于预算,返回上一个物品dp[i-1,j],即表格中同列上一行
dp[i, j] = dp[i, j];
}
else
{
// 物品价格小于预算,可以选择买或不买,分别求满意度取最大
// 买,满意度为买了a的满意度,加上,回到上一个物品再减去a的钱能买的最大满意度
int A = d + dp[i - 1, j - c];
// 不买,返回上个物品最大满意度
int B = dp[i, j];
dp[i, j] = max(A, B);
}
// e情况
if (e > j)
{
// 物品价格大于预算,返回上一个物品dp[i-1,j],即表格中同列上一行
dp[i, j] = dp[i, j];
}
else
{
// 物品价格小于预算,可以选择买或不买,分别求满意度取最大
// 买,满意度为买了a的满意度,加上,回到上一个物品再减去a的钱能买的最大满意度
int A = f + dp[i - 1, j - e];
// 不买,返回上个物品最大满意度
int B = dp[i, j];
dp[i, j] = max(A, B);
}
// g情况
if (g > j)
{
// 物品价格大于预算,返回上一个物品dp[i-1,j],即表格中同列上一行
dp[i, j] = dp[i, j];
}
else
{
// 物品价格小于预算,可以选择买或不买,分别求满意度取最大
// 买,满意度为买了a的满意度,加上,回到上一个物品再减去a的钱能买的最大满意度
int A = h + dp[i - 1, j - g];
// 不买,返回上个物品最大满意度
int B = dp[i, j];
dp[i, j] = max(A, B);
}
// 比较四个最大的
//dp[i, j] = max(a1, a2, a3, a4);
}
}
// 5.输出结果
Console.WriteLine(dp[N, M] * 10);
}
}
/// <summary>
/// 比较大小
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static int max(int a, int b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
public static int max(int a, int b, int c, int d)
{
int[] arr = new int[4] { a, b, c, d };
Array.Sort(arr);
return arr[0];
}
}
}