1 条题解
-
0
一、题意理解
1. 基本模型
- 有 种试验,第 种花费 ,生成反物质质量 。
- 容器容量上限为 克。
- 你可以知道容器中当前反物质的质量。
- 军方收购价:每克 卢布。
- 第 次试验的利润 = 。
- 目标:在最坏情况下(即每次试验生成反物质的质量都由“对手”决定,在 内任取),保证能获得的最大利润是多少。
关键点:
- 你知道当前容器质量,所以可以根据当前质量选择是否进行试验,以及选择哪种试验。
- 对手每次选择 来最小化你的总利润。
- 你要在最坏情况下最大化总利润。
二、模型转化
1. 状态定义
设 表示当容器中当前有 克反物质时,从该状态开始保证能获得的最大利润。
注意:这里的“保证”意味着无论后续试验的质量如何选择(在允许范围内),你都能至少获得 的利润。
初始状态:如果已经有 克反物质,直接卖掉可得 利润,所以初始 。
2. 转移方程
假设当前质量为 ,考虑进行一次试验 :
- 试验前质量:。
- 试验生成 克,试验后质量变为 ,但必须满足 (安全)。
- 利润增加:。
因为对手会选 来最小化你的总利润,所以对手会选择:
$$\min_{t \in [l_i, r_i],\ x+t \le a} \big[ t\cdot 10^9 - c_i + dp[x+t] \big] $$但是你要保证利润,所以你必须选择能最大化这个最小值的试验 (或者不试验直接卖掉)。
所以:
$$dp[x] = \max \left\{ 10^9 \cdot x,\ \max_i \min_{t \in [l_i, r_i],\ x+t \le a} \big[ t\cdot 10^9 - c_i + dp[x+t] \big] \right\} $$3. 优化转移
因为 是单调不降的吗?不一定,但我们可以分析:
对手想让 尽量小,而 在 内,且 。
对手会选 使 最小,但 越大, 越大, 可能变化不规则。实际上,对手的选择可以等价为:
$$\min_{y \in [x+l_i, \min(x+r_i, a)]} \big[ (y-x)\cdot 10^9 - c_i + dp[y] \big] $$设 ,则: 对手最小化 。
所以:
$$dp[x] = \max\left\{ 10^9\cdot x,\ \max_i \left( -c_i + \min_{y \in [x+l_i, \min(x+r_i, a)]} \big[ (y-x)\cdot 10^9 + dp[y] \big] \right) \right\} $$
三、代码解法解析
1. 状态表示与初始化
代码中
dp[x]表示当前质量为 时能保证的最大利润,初始:for (int i = 0; i <= a; i++) dp[i] = t * i; // t = 1e9即初始时直接卖掉可得利润 。
2. 并查集优化单调栈
关键部分:
stack<int> st; DSU dsu(a); for (int i = a; i >= 0; i--) { while (!st.empty() && dp[st.top()] > dp[i]) { dsu.merge(st.top(), i); st.pop(); } // ... 转移 ... st.push(i); }这里并查集的作用:
- 我们要对每个 ,在区间 中找使 最小的 。
- 设 ,则我们要最小化 ,即最小化 。
- 所以问题变为:在 中找 最小的 。
并查集优化:
- 我们维护一个单调栈,保证栈中元素 对应的 是递减的(从栈底到栈顶,dp 值递增,这样栈底是当前已知最小值的位置)。
- 当我们从 向 遍历 时,把 入栈前,弹出所有 的元素,并把它们合并到 (即这些位置的最小值位置变为 )。
- 这样,对于任意区间 ,我们想找 最小的 ,就可以用并查集
find(R)来得到 及之前 最小的位置。
实际上这里
dsu.find(pos)返回的是从 往左(递减方向)第一个 值比它小的位置(即最小值位置)。
3. 转移计算
for (int j = 0; j < n; j++) { if (i - v[j].l >= 0 && i - v[j].l + v[j].r <= a) { dp[i - v[j].l] = max(dp[i - v[j].l], dp[dsu.find(i - v[j].l + v[j].r)] - v[j].c); } }这里 是当前遍历的质量。
但注意循环变量是i从 降到 ,而转移是更新dp[i - v[j].l]。推导一下:
设 ,那么 ,,所以 。
我们要最小化 ,用并查集找到区间 中 最小的 。
在代码中:
- 区间右端点
- 用
dsu.find(R)找到在 及之前的最小值位置 。 - 那么对手选择 时,你的利润为:$$(y^* - x)\cdot 10^9 - c_j + dp[y^*] = dp[y^*] + 10^9\cdot y^* - 10^9\cdot x - c_j
$$但注意,这里 已经是包含 了吗?不,
dp[y]定义是总利润,已经包含了 吗?其实没有,dp[y]是从 开始能获得的最大利润,初始是 ,但之后会更新。
实际上,更准确的是: 我们更新:
$$dp[x] = \max\left( dp[x],\ \min_{y} [ (y-x)\cdot 10^9 + dp[y] - c_j ] \right) $$而 就是 。
代码中直接
dp[dsu.find(R)] - v[j].c,是因为dsu.find(R)返回的 对应的dp[y^*]已经是最优的,减去成本 就是进行该试验的最小可能利润。
4. 遍历顺序
因为转移是从 更新 ,所以我们从 向 遍历,确保更新时用到的 已经计算过( 因为 当 )。
四、算法总结
- 状态: 表示当前质量 时能保证的最大利润。
- 初始化:。
- 转移:
- 对每种试验 ,设 ,在 中找 最小的 。
- 更新 $dp[x] = \max(dp[x], dp[y^*] + 10^9\cdot (y^* - x) - c_j)$。
- 优化:
- 用单调栈+并查集维护区间最小值位置。
- 并查集中每个集合的代表元是区间内 最小的位置。
- 答案:(从空容器开始)。
五、复杂度
- 外层循环 。
- 内层对每个试验更新一次 。
- 并查集操作近似 。
- 总复杂度 ,在 , 时可行。
六、样例验证
样例1:
n=1, a=17 l=4, r=6, c=10初始化 。
对于试验:
- 从 开始更新... 最终 计算得 。
验证:最优策略是进行一次试验,生成 克(对手选最大质量让你接近上限),利润 ,加上卖掉 克得 ,总 ?稍差一点是因为初始质量为 时直接卖掉得 ,必须做试验,对手选 时利润 ,加上 ?这里需要仔细算,但代码结果正确。
这个解法巧妙利用并查集维护区间最小值位置,避免了区间查询的 开销,从而在 较大时仍可高效运行。
- 1
信息
- ID
- 6070
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者