[CF 2078D] Scammy Game Ad
前言
上午效率不高, 下午补一下之前的题
\(\textrm{div 2 D}\) , 综合训练, 然然后后做一下即可
今天复习
反正补题可以当问问区 \(\rm{up}\)
反正就是问问问之后就是写写写
- 定义操作 (约束) 和开销 / 收益, 要求最值化开销 / 收益
- 最优化问题的瓶颈, 考虑找最优解的性质来处理
- 逐元素处理
- 先找到统一的构造方式
- 直接处理
- 推导动态规划
- 枚举开销对应的值\((\)超过 \(x\) / 不大于 \(x\) / 长度为 \(x\)\()\) , 然后考虑对于这个值进行
- 贪心
- 判断合法性
- 往往拥有单调性
思路
题意
抖音弱智小游戏
图还是很有意思的

道门, 给定每道门左右两侧对应的操作 , 其中
最初左右两道只有各 人
每次操作增加的人数可以分配到任一车道, 但是, 已在一条车道上的人数不能被移动到另一条车道上
求最终两车道的人数之和最多是多少
不难发现一个性质, 因为乘法的可拆性, 每次额外可分配的人数放到一条路径上之后, 其本身的贡献是确定的
解释
这样说吧, 假设当前有 个人可以选择分配到任意一条道路上, 我们只需要知道其后有多少乘法操作, 就可以知道这一波人的贡献
而后面产生的新的可分配人的贡献又是另外计算的了
本质上就是乘法分配律
于是不难想到一个假做法, 每次分配的人贪心的放到后面贡献最大的道路上
看起来很正确, 实则不然
你发现这样做, 虽然保证了每次分配的人贡献最大, 但是不能保证能够被分配的人数最多
很有可能会出现有一些可以被分配的人没有被分配
怎么处理, 发现这已经不是简单的贪心了, 必须要出重拳!
类似于一个新问题, 如何移动才能使得当次分配的人总共产生的贡献最大\((\)也就是分出去的贡献也最大, 比较抽象\()\)
找性质, 发现最优解大概就是左右横跳, 尽可能吃到更多的乘法
考虑 \(\rm{dp}\)
设 \(f_{i, 0/1}\) 表示处于第 \(i\) 个门的 左/右, 在 \(i\) 后面的最优分配方法带来的倍数收益
初始化 \(f_{i, 0/1}\) 为当前这个门的乘法系数\((\)没有设为 \(1\)\()\)
然后每次贪心的选择更优的那个路径即可
代码
#include <cstdio>
#include <algorithm>
#include <utility> // for std::pair
#define endl '\n'
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 100005;
// 定义操作结构体
struct Operation {
char type; // 操作符类型 ('x' 或 '+')
ll val; // 操作数值
};
// 用 pair 封装左右操作
std::pair<Operation, Operation> ops[MAXN];
ll count_left[MAXN] = {1};
ll count_right[MAXN] = {1};
ll f[MAXN][2] = {0};
int main() {
ll t;
scanf("%lld", &t);
while (t--) {
ll n;
scanf("%lld", &n);
count_left[0] = count_right[0] = 1;
// 输入处理
for (ll i = 1; i <= n; ++i) {
Operation op_left, op_right;
// 注意 val 用 %lld 读取
scanf(" %c %lld %c %lld",
&op_left.type, &op_left.val,
&op_right.type, &op_right.val);
ops[i] = {op_left, op_right};
// 计算累积值
count_left[i] = count_left[i - 1];
count_right[i] = count_right[i - 1];
if (ops[i].first.type == 'x') count_left[i] *= ops[i].first.val;
if (ops[i].second.type == 'x') count_right[i] *= ops[i].second.val;
}
// 动态规划计算
for (ll i = n; i >= 1; --i) {
ll t1 = count_left[i] / count_left[i - 1];
ll t2 = count_right[i] / count_right[i - 1];
f[i][0] = t1;
f[i][1] = t2;
if (i != n) {
f[i][0] = std::max(f[i][0] * f[i + 1][0],
f[i + 1][0] + (f[i][0] - 1) * f[i + 1][1]);
f[i][1] = std::max(f[i][1] * f[i + 1][1],
f[i + 1][1] + (f[i][1] - 1) * f[i + 1][0]);
}
}
ll ans_left = 1, ans_right = 1;
// 计算结果
for (ll i = 1; i <= n; ++i) {
const auto& [op_left, op_right] = ops[i]; // C++17 结构化绑定
ll add = 0;
// 处理左操作
if (op_left.type == 'x')
add += ans_left * (op_left.val - 1);
else
add += op_left.val;
// 处理右操作
if (op_right.type == 'x')
add += ans_right * (op_right.val - 1);
else
add += op_right.val;
// 分配结果
if (i != n) {
if (f[i + 1][0] >= f[i + 1][1])
ans_left += add;
else
ans_right += add;
} else {
ans_left += add;
}
}
printf("%lld\n", ans_left + ans_right);
}
return 0;
}
总结
生成的次问题, 同样可以用方法解决
反正就这样那样的, 呃呃呃
常数条路径互相跳, 求贡献最优这种问题, 往往可以类似的解决
一类不知道应该怎么决策的问题, 可以尝试用 \(\rm{dp}\) 辅助解决
- 决策种类有限的问题, 求最优花费
- 不限顺序
- 区间 \(\rm{dp}\)
- 限制顺序
- 线性 \(\rm{dp}\)
- 不限顺序
好像比较局限