P11086 [ROI 2019] 机器人高尔夫 (Day 2) 题解
简要题意
给出一个
n\times m 的矩形网格,上面有k 个洞。A 和 B 两个人轮流推着一个球在网格上移动,每次可以使得球向右或者向下移动一步。当球被推出边界或者推到一个洞时游戏结束,如果被推出边界得分为0 ,如果被推到一个洞里得分就是这个洞的权值。A 想使得分尽可能小,B 想使游戏得分尽可能大,A 先手,设g(x,y) 为从(x,y) 开始游戏最终获得的分数,求\displaystyle \sum_{i=1}^n\sum_{j=1}^mg(i,j) 。
暴力做法
我们考虑游戏进行的过程,很容易想到一个显然的 DP:
- 设计状态
dp_{i,j,0/1} 表示现在处于点(i,j) ,当前是玩家 A 还是 B 进行操作。 - 如果在点
(i,j) 上面有一个权值为a_{i,j} 的洞,那么dp_{i,j,0}\gets a_{i,j}\ ,dp_{i,j,1}\gets a_{i,j} 。 - 如果当前点
(i,j) 上面没有洞,那么dp_{i,j,0}\gets \min(dp_{i+1,j,1},dp_{i,j+1,1}),dp_{i,j,1}\gets \max(dp_{i+1,j,0},dp_{i,j+1,0}) 。
状态数量为
优化
考虑使用两种颜色给矩形网格进行染色,设
这样操作之后状态数没有改变,但是有助于进行进一步的转化。
此时假设当前的格子轮到 A 来走,而且当前格子的右边和下边都没有洞,那么 DP 的转移式可以这样转化:
这可以等价为
通过这个式子可以发现,一个位于
这是一个关键的观察,它启示我们用维护
所以最终的做法是,我们预处理出那 std::vector 存储并且降序排序。枚举 A 是走颜色为 std::vector 里面被洞直接影响到的点,使用 std::map 来维护当前
int n, m, k;
ll res;
map<int, int> dp, pos;
map<pair<int, int>, int> val;
vector<pair<int, int> > v;
inline ll query(int x, int y) {
if (!dp.count(x - y))return 0;
return dp[x - y];
}
void calc(bool flag) {
dp.clear(), pos.clear();
for (auto [x, y] : v) {
if (flag == (x + y & 1) && dp.count(x - y)) {
(res += 1ll * dp[x - y] * (pos[x - y] - x)) %= mod;
}
if (val.count(make_pair(x, y))) {
dp[x - y] = val[make_pair(x, y)];
} else {
dp[x - y] = flag == (x + y & 1) ?
min(query(x + 1, y), query(x, y + 1)) :
max(query(x + 1, y), query(x, y + 1));
}
pos[x - y] = x;
}
for (auto [i, j] : dp) {
if (flag == (2 * pos[i] - i & 1)) {
(res += 1ll * min(pos[i], pos[i] - i) * j) %= mod;
}
}
}
void solve() {
fr(n, m, k);
for (int i = 1; i <= k; i++) {
int x, y, z;
fr(x, y, z);
v.emplace_back(x, y);
val[make_pair(x, y)] = z;
for (int dx = 0; dx <= 2; dx++) {
for (int dy = 0; dx + dy <= 2; dy++) {
if (x > dx && y > dy) {
v.emplace_back(x - dx, y - dy);
}
}
}
}
sort(v.begin(), v.end(), greater());
v.erase(unique(v.begin(), v.end()), v.end());
calc(0);
calc(1);
fw((res + mod) % mod), pc('\n');
}