思路
题意
给定一个 的方格图, 每个方格可以是
- 空地
- 房屋
一共有 个 - 墙壁
定义两个房屋 之间的距离为
从 途径一些房屋 可以不经过 到达
即 , 两个房屋之间经过的空地数量记为
最终的距离即为
给定 次询问, 每次询问两个房屋之间的距离最小值
朴素想法是对于每两个房屋连边, 然后通过最小生成树解决
显然的, 连边的复杂度直接趋势, 但是我们不难发现, 每个点只需要连上可能在最小生成树上出现的边即可, 我们多源 \(\rm{bfs}\) , 每个点只连接与自己最近的边即可
所以我们连边 \(\mathcal{O} (nm)\)
建立最小生成树 \(\mathcal{O} (p^2)\) , 边的条数是 \(\mathcal{O} (nm)\) 的, 所以用优化的 \(\rm{prim}\) 做到 \(\mathcal{O} (nm \log p)\)
查询是 \(\rm{LCA}\) , 一共是 \(\mathcal{O} (q \log p)\)
实现
不好是码力题
框架
\(\rm{bfs}\)
每个点往外染色的同时记录距离, 碰上了就连边即可, 不管重边即可, 也管不了
最小生成树
新图上做 \(\rm{prim}\) 即可, 记录哪些边在 \(\rm{MST}\) 上, 最后新建一颗树
\(\rm{LCA}\)
倍增往上跳即可, 路上记录最小值, 在 \(\rm{LCA}\) 处合并
这个同样用倍增合并即可
代码
#include <bits/stdc++.h>
const int MAXN = 2005;
const int MAXM = 10000000;
const int MAXP = 2e5 + 20;int n, m, p, q;
int mp[MAXN][MAXN];
std::pair<int, int> pos[MAXP]; // i 座房屋的位置/*连边 + 建立最小生成树*/
class mstbuilder
{
private:/*图*/struct graph {struct node { int from, to, nxt, w; } edge[MAXM << 2]; int cnt = -1; int head[MAXP];void head_init() { memset(head, -1, sizeof head); }void addedge(int u, int v, int w) { edge[++cnt].from = u, edge[cnt].to = v, edge[cnt].w = w, edge[cnt].nxt = head[u], head[u] = cnt; }} gra; // 初始图std::pair<int, int> col[MAXN][MAXN];int dx[5] = {0, -1, 0, 1, 0}, dy[5] = {0, 0, -1, 0, 1};std::queue<std::pair<int, int>> Q;/*检查是否在地图*/ bool check(int x, int y) { return (x >= 1 && x <= n) && (y >= 1 && y <= m); }/*多源 bfs*/void bfs() {while (!Q.empty()) Q.pop();/*加入初始点*/ for (int i = 1; i <= p; i++) { Q.push(pos[i]); col[pos[i].first][pos[i].second] = {i, 0}; }while (!Q.empty()) {std::pair<int, int> tmp = Q.front(); Q.pop();/*拓展一层*/int x = tmp.first, y = tmp.second;for (int op = 1; op <= 4; op++) {int nx = x + dx[op], ny = y + dy[op];if (!check(nx, ny) || !(~mp[nx][ny])) continue;/*连边*/if (col[nx][ny].first) {if (col[nx][ny].first == col[x][y].first) continue;int w = col[nx][ny].second + col[x][y].second;gra.addedge(col[nx][ny].first, col[x][y].first, w), gra.addedge(col[x][y].first, col[nx][ny].first, w);} else col[nx][ny].first = col[x][y].first, col[nx][ny].second = col[x][y].second + 1, Q.push({nx, ny});}}}struct node {int id, dis, e; // 点的标号和边的长度, 编号node(int a, int b, int c) { id = a, dis = b, e = c; }friend bool operator < (const node &a, const node &b) { return a.dis > b.dis; }} ;std::priority_queue<node> PQ;std::vector<int> inMST;void prim(int st) {while (!PQ.empty()) PQ.pop(); PQ.push(node(st, 0, -1));int tmp = 0;while (!PQ.empty()) {node u = PQ.top(); PQ.pop();if (done[u.id]) continue; done[u.id] = cnt, tmp++;if (~u.e) inMST.push_back(u.e);for (int e = gra.head[u.id]; ~e; e = gra.edge[e].nxt) {node v (gra.edge[e].to, gra.edge[e].w, e);if (done[v.id]) continue;PQ.push(v);}}}public:/*建立初始图*/void buildgra() { gra.head_init(); bfs(); }graph mst; // 最小生成树int done[MAXP], cnt = 0; // 在哪颗 MST 中void buildmst() {memset(done, 0, sizeof done); mst.head_init();for (int i = 1; i <= p; i++)if (!done[i]) { inMST.clear(), ++cnt; prim(i); for (auto e : inMST) {int u = gra.edge[e].from, v = gra.edge[e].to, w = gra.edge[e].w;mst.addedge(u, v, w), mst.addedge(v, u, w);}}}
} bd;/*LCA 求答案*/
class solution
{
private:mstbuilder* bd_ptr;struct node {int fa[MAXP][20], lev[MAXP][20]; // 倍增数组int dep[MAXP];node() { memset(fa, 0, sizeof fa), memset(lev, 0, sizeof lev), memset(dep, 0, sizeof dep); }} mst;bool vis[MAXP];/*处理倍增数组 + 深度数组*/void dfs1(int u, int fat, int faw) {mst.dep[u] = mst.dep[fat] + 1;mst.fa[u][0] = fat; mst.lev[u][0] = faw;/*处理 fa*/ for (int i = 1; (1 << i) <= mst.dep[u]; i++) mst.fa[u][i] = mst.fa[mst.fa[u][i - 1]][i - 1];/*处理 lev*/ for (int i = 1; (1 << i) <= mst.dep[u]; i++) mst.lev[u][i] = std::max(mst.lev[u][i - 1], mst.lev[mst.fa[u][i - 1]][i - 1]);for (int e = bd_ptr->mst.head[u]; ~e; e = bd_ptr->mst.edge[e].nxt) {if (bd_ptr->mst.edge[e].to == fat) continue;dfs1(bd_ptr->mst.edge[e].to, u, bd_ptr->mst.edge[e].w);}}/*预处理需要的信息*/ void init() { for (int i = 1; i <= p; i++) if (!vis[bd_ptr->done[i]]) { vis[bd_ptr->done[i]] = true; mst.dep[0] = -1; dfs1(i, 0, 0);} }/*找到 LCA 并返回结果*/int LCA(int u, int v) {if (mst.dep[u] < mst.dep[v]) std::swap(u, v);int ans = 0;/*提到一起*/ for (int i = 19; i >= 0; i--) if (mst.dep[u] - (1 << i) >= mst.dep[v]) ans = std::max(ans, mst.lev[u][i]), u = mst.fa[u][i];/*同步上提*/ for (int i = 19; i >= 0; i--) if (mst.fa[u][i] != mst.fa[v][i]) ans = std::max(ans, mst.lev[u][i]), ans = std::max(ans, mst.lev[v][i]), u = mst.fa[u][i], v = mst.fa[v][i];return ans = ((u != v) ? std::max({ans, mst.lev[u][0], mst.lev[v][0]}) : ans);}public:solution() : bd_ptr(nullptr) {}solution(mstbuilder* bd) : bd_ptr(bd) {}/*处理问题*/void solve() {init();while (q--) {int u, v; scanf("%d %d", &u, &v);/*不连通*/ if (bd_ptr->done[u] != bd_ptr->done[v]) { printf("-1\n"); continue; }/*处理 LCA*/ printf("%d\n", LCA(u, v));}}} sol;int main()
{scanf("%d %d %d %d", &n, &m, &p, &q);char tmp; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { std::cin >> tmp; mp[i][j] = (tmp == '.' ? 0 : -1); }int tx, ty; for (int i = 1; i <= p; i++) { scanf("%d %d", &tx, &ty); pos[i] = {tx, ty}; mp[tx][ty] = i; }bd.buildgra();bd.buildmst();solution sol(&bd);sol.solve();return 0;
}
总结
两点间的最值型参数的最值化问题, 往往使用最值生成树
常用的简化最小生成树前的建图方式 : 多源 \(\rm{bfs}\)
非常好倍增, 爱来自瓷器