H. The Most Reckless Defense
You are playing a very popular Tower Defense game called "Runnerfield 2". In this game, the player sets up defensive towers that attack enemies moving from a certain starting point to the player's base.
You are given a grid of size $n \times m$, on which $k$ towers are already placed and a path is laid out through which enemies will move. The cell at the intersection of the $x$-th row and the $y$-th column is denoted as $(x, y)$.
Each second, a tower deals $p_i$ units of damage to all enemies within its range. For example, if an enemy is located at cell $(x, y)$ and a tower is at $(x_i, y_i)$ with a range of $r$, then the enemy will take damage of $p_i$ if $(x - x_i) ^ 2 + (y - y_i) ^ 2 \le r ^ 2$.
Enemies move from cell $(1, 1)$ to cell $(n, m)$, visiting each cell of the path exactly once. An enemy instantly moves to an adjacent cell horizontally or vertically, but before doing so, it spends one second in the current cell. If its health becomes zero or less during this second, the enemy can no longer move. The player loses if an enemy reaches cell $(n, m)$ and can make one more move.
By default, all towers have a zero range, but the player can set a tower's range to an integer $r$ ($r > 0$), in which case the health of all enemies will increase by $3^r$. However, each $r$ can only be used for at most one tower.
Suppose an enemy has a base health of $h$ units. If the tower ranges are $2$, $4$, and $5$, then the enemy's health at the start of the path will be $h + 3 ^ 2 + 3 ^ 4 + 3 ^ 5 = h + 9 + 81 + 243 = h + 333$. The choice of ranges is made once before the appearance of enemies and cannot be changed after the game starts.
Find the maximum amount of base health $h$ for which it is possible to set the ranges so that the player does not lose when an enemy with health $h$ passes through (without considering the additions for tower ranges).
Input
The first line contains an integer $t$ ($1 \le t \le 100$) — the number of test cases.
The first line of each test case contains three integers $n$, $m$, and $k$ ($2 \le n, m \le 50, 1 \le k < n \cdot m$) — the dimensions of the field and the number of towers on it.
The next $n$ lines each contain $m$ characters — the description of each row of the field, where the character "." denotes an empty cell, and the character "#" denotes a path cell that the enemies will pass through.
Then follow $k$ lines — the description of the towers. Each line of description contains three integers $x_i$, $y_i$, and $p_i$ ($1 \le x_i \le n, 1 \le y_i \le m, 1 \le p_i \le 500$) — the coordinates of the tower and its attack parameter. All coordinates correspond to empty cells on the game field, and all pairs $(x_i, y_i)$ are pairwise distinct.
It is guaranteed that the sum of $n \cdot m$ does not exceed $2500$ for all test cases.
Output
For each test case, output the maximum amount of base health $h$ on a separate line, for which it is possible to set the ranges so that the player does not lose when an enemy with health $h$ passes through (without considering the additions for tower ranges).
If it is impossible to choose ranges even for an enemy with $1$ unit of base health, output "0".
Example
input
6
2 2 1
#.
##
1 2 1
2 2 1
#.
##
1 2 2
2 2 1
#.
##
1 2 500
3 3 2
#..
##.
.##
1 2 4
3 1 3
3 5 2
#.###
#.#.#
###.#
2 2 2
2 4 2
5 5 4
#....
#....
#....
#....
#####
3 2 142
4 5 9
2 5 79
1 3 50
output
0
1
1491
11
8
1797
Note
In the first example, there is no point in increasing the tower range, as it will not be able to deal enough damage to the monster even with $1$ unit of health.
In the second example, the tower has a range of $1$, and it deals damage to the monster in cells $(1, 1)$ and $(2, 2)$.
In the third example, the tower has a range of $2$, and it deals damage to the monster in all path cells. If the enemy's base health is $1491$, then after the addition for the tower range, its health will be $1491 + 3 ^ 2 = 1500$, which exactly equals the damage the tower will deal to it in three seconds.
In the fourth example, the tower at $(1, 2)$ has a range of $1$, and the tower at $(3, 1)$ has a range of $2$.
解題思路
解決這題最關鍵的地方是要觀察到半徑 $r$ 最大能取到的值非常小。對於一座具有半徑 $r$ 的塔,它最多能造成的傷害為 $\pi r^2 \cdot p_i \leq nm \cdot p_i \leq 50^2 \cdot 500$。又因為在選擇 $r$ 後敵人的生命值會增加 $3^r$,因此當 $3^r$ 超過能造成的傷害時,這樣的 $r$ 是沒有意義的。即 $r$ 應該滿足 $3^r \leq 50^2 \cdot 500 \Rightarrow r \leq \log_3{\left( 50^2 \cdot 500 \right)} \approx 12$。
所以現在的問題是如何把這 $12$ 個不同的半徑 $r$ 分配給 $k$ 座塔(不一定要全都選擇),使得所有塔能造成的傷害最大。對於每種分配方案,傷害最大值減去該方案增加的生命值就是敵人初始最大能取到的生命值,對每個方案取最大值就是答案。
可以用狀壓 dp 來解決。定義 $f(i,j)$ 表示將二進位制集合 $j$ 中的半徑(第 $i$ 位是 $1$ 表示選擇了長度為 $i$ 的半徑。沒有長度為 $0$ 的半徑)分配給前 $i$ 座塔的所有方案中造成傷害的最大值。根據第 $i$ 座塔選擇了哪個半徑進行狀態劃分。狀態轉移方程為:$$f(i,j)=\max\left\{{f(i-1,j),\;\max_{\begin{align*}1 \leq r \leq 12 \\ j(r)=1 \end{align*}}\left\{{f\left(i-1,j \oplus 2^r\right) + s(i,r)}\right\}}\right\}$$
其中 $j(r)=1$ 表示 $j$ 在二進位制下的第 $r$ 位為 $1$。$s(i,u)$ 表示第 $i$ 座塔在半徑長度 $r$ 的範圍內造成的總傷害,可以透過 $O(k \cdot nm)$ 的複雜度暴力預處理所有的 $s(i,r), \, (1 \leq i \leq k, 1 \leq r \leq 12)$。
最後答案就是 $\max\limits_{j}\left\{{f(k,j) - \sum\limits_{j(r)=1}{3^r}}\right\}$。
AC 程式碼如下,時間複雜度為 $O \left( k \cdot nm + k \cdot 2^r \cdot r \right)$,其中 $r = \log_3{(P \cdot nm)}$,這裡直接取成 $12$。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 55, M = N * N, K = 13;
char g[N][N];
LL f[M][1 << K], s[M][K];
bool check(int x1, int y1, int x2, int y2, int r) { // 計算點(x2,y2)是否在以(x1,y1)為圓心r為半徑的圓內
int dx = x1 - x2, dy = y1 - y2;
return dx * dx + dy * dy <= r * r;
}
void solve() {
int n, m, k;
scanf("%d %d %d", &n, &m, &k);
for (int i = 1; i <= n; i++) {
scanf("%s", g[i] + 1);
}
for (int i = 1; i <= k; i++) {
int x, y, w;
scanf("%d %d %d", &x, &y, &w);
for (int j = 0; j <= 12; j++) {
s[i][j] = 0;
for (int u = 1; u <= n; u++) {
for (int v = 1; v <= m; v++) {
if (check(x, y, u, v, j) && g[u][v] == '#') s[i][j] += w;
}
}
}
}
for (int i = 1; i <= k; i++) {
for (int j = 0; j < 1 << 13; j++) {
f[i][j] = f[i - 1][j] + s[i][0];
for (int k = 1; k <= 12; k++) {
if (j >> k & 1) f[i][j] = max(f[i][j], f[i - 1][j ^ 1 << k] + s[i][k]);
}
}
}
LL ret = 0;
for (int i = 0; i < 1 << 13; i++) {
LL t = f[k][i], p = 1;
for (int j = 1; j <= 12; j++) {
p *= 3;
if (i >> j & 1) t -= p;
}
ret = max(ret, t);
}
printf("%lld\n", ret);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
參考資料
Codeforces Round 938 (Div. 3) Editorial:https://codeforces.com/blog/entry/128243