首先可以根据字符串 \(D\),\(\mathcal{O}(2^c|D|)\)(\(c\) 为方向数 \(4\))求出上下左右分别是否被感染时对应的最长连续段长度,用于后面的 check。
考虑到答案要求的最小值,于是可以考虑思考什么样的点不会作为最后的最小值的起始点。
考虑到如果最先感染了点 \(u\),且最终感染了点 \(v\),但是若最先感染了点 \(v\) 却不会感染 \(u\),那么 \(u\) 一定是不优的。
这是因为以 \(v\) 为起始点最后能走到的点的点集一定会是以 \(u\) 为起始点的点集的子集。
于是考虑图论刻画,如果以 \(u\) 为起始点可以感染到 \(v\),那么连边 \(u\to v\)。
那么这个连边能发现肯定是有传递性的,即 \(u\to v, v\to w\),则必定有 \(u\to w\)。
那么对该有向图缩完点后,可以知道答案必定出现在一个无出边的强连通分量中(有出边往出边走一定更优),且这个强连通分量一定是个团。
所以答案就是这种团的最小大小,而起始点数就是最小大小乘上大小为该大小的这种团的数量。
于是来考虑如何维护这个图和最后的答案的团。
一个想法是肯定这个团对应到网格图上是一个连通块。
于是考虑维护图上的若干个连通块与其对应的为答案的团的点集。
然后去合并连通块,同时合并答案的团的点集。
需要注意的是,这里维护的答案的团的点集只针对当前连通块,并不保证合并之后依然如此(可能答案点集的点不在答案中,不在答案点集的点在答案中)。
且同时,保证连通块内的任意一个点开始扩展都可以扩展到 \(v\)(就可以扩展到整个答案集)。
但是直接维护这个团的点显得有点太麻烦了,考虑用其他的东西去刻画这个团。
能够知道的是,因为这是个团,且不会连向团外的其他点,所以只需要知道这个团内的任意一个点,就可以知道这个团里的点。
即从这个点开始 BFS 往外扩展这个团就可以了。
于是对于一个连通块,可以直接用一个关键点 \(u\) 来表示团。
那接下来就来考虑合并连通块了。
考虑如果以关键点 \(u\) 开始往外扩展团,扩展完团后发现还能继续扩展到另一个连通块,令这一个连通块的关键点是 \(v\)。
因为 \(v\) 所在的连通块内的任意一个点开始扩展都能扩展到 \(v\),那么从 \(u\) 扩展就一定可以扩展到 \(v\)。
那么就可以直接把 \(u\) 对应的连通块合并到 \(v\) 对应的连通块,且这个时候因为只知道 \(u\) 可以扩展到 \(v\),不知道 \(v\) 是否可以扩展到 \(u\),所以令新的连通块的关键点依然为 \(v\)。
如果无法扩展,那么说明当前跑出来的团就是这个团的答案,且不再能扩展了,直接统计这个连通块的贡献即可。
但是如果直接每次选一个没扩展的点向外扩展维护该连通块的关键点对于复杂度不太可接受,因为每次合并关键点就不是当前关键点了,只能重新更换新的关键点跑。
但是注意到同时合并多个连通块是不会对过程产生影响的,那么可以想到 boruvka。
具体来说,可以分轮跑,其中对于每一轮,就为每一个连通块都找到一个要合并上的连通块,然后同时合并,这样子每一轮过后连通块数必定最多是原来的一半,就只会有 \(\mathcal{O}(\log nm)\) 轮。
其实这里应该细说一下同时合并的正确性:
考虑如果连通块 \(u\) 要合并到连通块 \(v\),关键点仍为 \(v\),连边 \(u\to v\),那么这是一颗树 / 基环树:
- 一颗树。那么对于树边,这相当于一个传递关系传递到根,其中树根已经无法扩展出去了,直接统计贡献一定是没有问题的。
- 一棵基环树。
首先对于树,同上就是传递关系传递到环上的节点,没有问题。
对于环,假设是 \(p_1\to p_2\to \cdots p_n\to p_1\),那么不管从 \(p_{1\sim n}\) 的任何一个点开始,肯定都能扩展到 \(p_{1\sim n}\),于是 \(p_{1\sim n}\) 任意一个点作为关键点都是可行的,所以没有问题。
于是就做完啦,时间复杂度 \(\mathcal{O}(2^c |D| + nmc\log nm)\),其中 \(c = 4\),含义为方向数。
代码写的有点丑了,后面一部分写成了 \(\mathcal{O}(nmc^2\log nm)\)。
#include<bits/stdc++.h>
constexpr int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
constexpr int maxL = 2e5 + 10, maxn = 800 + 10;
char str_[maxL], C[5] = "NSWE";
int str[maxL], mxt[16];
int U[maxn][maxn];
int fx[maxn][maxn], fy[maxn][maxn];
int vis[maxn][maxn], T;
inline std::pair<int, int> getfa(int x, int y) {if (fx[x][y] != x || fy[x][y] != y) {std::tie(fx[x][y], fy[x][y]) = getfa(fx[x][y], fy[x][y]);}return std::make_pair(fx[x][y], fy[x][y]);
}
int main() {int L, n, m;scanf("%d%d%d%s", &L, &n, &m, str_);for (int i = 0; i < L; i++) {while (C[str[i]] != str_[i]) str[i]++;}for (int s = 0; s < 16; s++) {int c = 0;for (int i_ = 0; i_ < L + L; i_++) {int i = i_ % L;if (s >> str[i] & 1) {c++;} else {mxt[s] = std::max(mxt[s], c), c = 0;}}mxt[s] = std::max(mxt[s], c);}for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {scanf("%d", &U[i][j]);if (U[i][j]) {U[i][j] = std::min(U[i][j], L);std::tie(fx[i][j], fy[i][j]) = std::make_pair(i, j);} else {U[i][j] = 1e9;}}}int ans = 1e9, cnt = 0;while (true) {std::vector<std::pair<int, int> > S;for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) {if (std::make_pair(i, j) == getfa(i, j)) {S.emplace_back(i, j);}}if (S.empty()) break;std::vector<std::pair<int, int> > to(S.size(), std::make_pair(-1, -1));for (size_t id = 0; id < S.size(); id++) {auto [sx, sy] = S[id];std::queue<std::pair<int, int> > Q;vis[sx][sy] = ++T, Q.emplace(sx, sy);int siz = 0;while (! Q.empty()) {auto [i, j] = Q.front(); Q.pop();siz++;for (int k : {0, 1, 2, 3}) {int x = i + dx[k], y = j + dy[k];if (x < 1 || y < 1 || x > n || y > m || vis[x][y] == T) continue;int s = 0;for (int h : {0, 1, 2, 3}) {int px = x + dx[h], py = y + dy[h];if (px < 1 || py < 1 || px > n || py > m) continue;s |= (vis[px][py] == T) << h;}if (U[x][y] <= mxt[s]) {vis[x][y] = T;if (std::make_pair(sx, sy) == getfa(x, y)) {Q.emplace(x, y);} else {to[id] = {x, y};}}}}if (to[id] == std::make_pair(-1, -1)) {if (siz < ans) ans = siz, cnt = 0;if (siz == ans) cnt += ans;std::tie(fx[sx][sy], fy[sx][sy]) = std::make_pair(0, 0);}}for (size_t i = 0; i < S.size(); i++) {if (to[i] != std::make_pair(-1, -1)) {auto [sx, sy] = S[i];auto [tx, ty] = to[i];std::tie(fx[sx][sy], fy[sx][sy]) = getfa(tx, ty);}}}printf("%d\n%d\n", ans, cnt);return 0;
}