Loading

2.11 CW 模拟赛 T4. 刀压电线

思路

题意

给定一个 n×mn \times m (1n,m2000)(1 \leq n, m \leq 2000) 的方格图, 每个方格可以是

  • 空地
  • 房屋
    一共有 p(1p2×105)p (1 \leq p \leq 2 \times 10^5)
  • 墙壁

定义两个房屋 u,vu, v 之间的距离为

uu 途径一些房屋 ((可以不经过)) 到达 vv
um1m2mkvu \to m_1 \to m_2 \to \cdots \to m_k \to v , 两个房屋之间经过的空地数量记为 xx
最终的距离即为 min{x1}\min \{x - 1\}

给定 q(1q2×105)q (1 \leq q \leq 2 \times 10^5) 次询问, 每次询问两个房屋之间的距离最小值

朴素想法是对于每两个房屋连边, 然后通过最小生成树解决
显然的, 连边的复杂度直接趋势, 但是我们不难发现, 每个点只需要连上可能在最小生成树上出现的边即可, 我们多源 \(\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}\)

非常好倍增, 爱来自瓷器

posted @ 2025-02-13 09:29  Yorg  阅读(16)  评论(0)    收藏  举报