Codeforces Gym 101173 H. Hangar Hurdles (LCA + 并查集)

41 篇文章 0 订阅
7 篇文章 0 订阅

题意

.# 所组合成的矩形 N×N 地图。 kij 表示以 (i, j) 为中心的最大正方形(正方形范围内全是 . )的边长为 k 。

问对于每个询问从 (sx, sy)(ex, ey) 的过程中最大的正方形边长 k ,使得满足在每一时刻正方形所包含的区域内均不存在 # 同时不会越界。

解题思路

首先通过洪泛算法求解每个点的 k 。具体过程见 flood_fill()

按照 k 值从大到小进行枚举,当枚举点 i 时,向四个方向扩展,若点 j 已经访问过,找到与它同集合的最祖先节点 aj,从 i 向 aj 连边,同时将 i 通过并查集合并。

对于每组询问,通过 LCA 求最近公共祖先,祖先点的 k 值即为答案的最大值。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
const int dir[8][2] = {-1,0, 1,0, 0,-1, 0,1, -1,-1, -1,1, 1,-1, 1,1};
int n, q, k[N][N], vis[N][N];
char flr[N][N];
pair<int, int> arr[N*N];
bool cmp(pair<int, int> a, pair<int, int> b) {
    return a.second > b.second;
}

struct Edge {
    int nxt, to;
} e[N*N*2];
int head[N*N], cnt;
void addedge(int u, int v) {
    e[++cnt].nxt = head[u];
    e[cnt].to = v;
    head[u] = cnt;
}

int fa[N*N];
int find(int x)  {   return fa[x] = fa[x] == x ? x : find(fa[x]);    }

void flood_fill()
{
    queue<pair<int, int> > que;
    pair<int, int> p, q;
    memset(k, 0x3f, sizeof(k));
    for(int i=0;i<=n+1;i++)
    for(int j=0;j<=n+1;j++)
        if(flr[i][j] == '#' || i==0 || i==n+1 || j==0 || j==n+1)
            que.push(make_pair(i, j)),  k[i][j] = -1;
    while(!que.empty())
    {
        p = que.front();
        que.pop();
        for(int i=0;i<8;i++)
        {
            q = make_pair(p.first+dir[i][0], p.second+dir[i][1]);
            if(q.first<=0 || q.first>n || q.second<=0 || q.second>n || k[q.first][q.second] != 0x3f3f3f3f)  continue;
            k[q.first][q.second] = k[p.first][p.second] + 2;
            que.push(q);
        }
        if(k[p.first][p.second] == -1)  k[p.first][p.second] = 0;
    }
}

void solve(int r, int c)
{
    vis[r][c] = 1;
    for(int i=0, nr, nc;i<4;i++)
    {
        nr = r + dir[i][0];
        nc = c + dir[i][1];
        if(nr<=0 || nr>n || nc<=0 || nc>n || vis[nr][nc] == 0)  continue;
        int x = find((nr-1)*n+nc-1);
        int y = find((r-1)*n+c-1);
        if(x == y)  continue;
        fa[x] = y;
        addedge(y, x);  addedge(x, y);
    }
}

const int maxh = 20;
int dep[N*N];
int anc[N*N][20];
void dfs(int rt) {
    static int Stack[N*N];
    int top = 0;
    dep[rt] = 1;
    for(int i=0;i<maxh;i++)
        anc[rt][i] = rt;
    Stack[++top] = rt;
    while(top) {
        int x = Stack[top];
        if(x != rt) {
            for(int i=1, y;i<maxh;i++)
                y = anc[x][i-1],    anc[x][i] = anc[y][i-1];
        }
        for(int &i=head[x];i;i=e[i].nxt) {
            int y = e[i].to;
            if(y != anc[x][0]) {
                dep[y] = dep[x] + 1;
                anc[y][0] = x;
                Stack[++top] = y;
            }
        }
        while(top && head[Stack[top]] == 0) top--;
    }
}
void swim(int &x, int H) {
    for(int i=0;H;i++) {
        if(H & 1)   x = anc[x][i];
        H /= 2;
    }
}
int lca(int x, int y) {
    int i;
    if(dep[x] > dep[y]) swap(x, y);
    swim(y, dep[y]-dep[x]);
    if(x == y)  return x;
    for(;;) {
        for(i=0;anc[x][i] != anc[y][i];i++);
        if(i == 0)  return anc[x][0];
        x = anc[x][i-1];
        y = anc[y][i-1];
    }
    return -1;
}

int main()
{
    while(scanf("%d", &n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf(" %s", flr[i]+1);
        // 计算每个点的最大 k .
        flood_fill();

        for(int i=1, x;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            x = (i-1)*n+j-1;
            arr[x].first = x;
            arr[x].second = k[i][j];
        }
        sort(arr, arr+n*n, cmp);

        for(int i=0;i<=n*n;i++)
            fa[i] = i;
        memset(vis, 0, sizeof(vis));
        for(int i=0, x;i<n*n;i++) {
            x = arr[i].first;
            solve(x/n+1, x%n+1);
        }
        dfs(arr[n*n-1].first);

        scanf("%d", &q);
        for(int i=0, sx, sy, ex, ey;i<q;i++)
        {
            scanf("%d %d %d %d", &sx, &sy, &ex, &ey);
            int pointIdx = lca((sx-1)*n+sy-1, (ex-1)*n+ey-1);
            printf("%d\n", k[pointIdx/n+1][pointIdx%n+1]);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值