[BZOJ3204][Sdoi2013]城市规划(线段树+并查集)

这道数据结构题真的变态。
看到 M M 6 N N 105 ,想到使用线段树来维护行,又由于询问与连通块有联系,因此又想到并查集。
具体地,线段树每个节点如果表示区间 [l,r] [ l , r ] ,则它要储存:
ans a n s : 第 l l 行到第 r 行,包含建筑的连通块个数。
set s e t :一个并查集,储存了第 l l M 个格子和第 r r M 个格子共 2M 2 M 个格子的连通性,以及每个连通块是否与建筑物连通。
而此题的变态之处就是两个子节点的合并。
合并时,先把两个子节点的 ans a n s 加起来。然后把两个儿子的四个边界放在一起,对于左儿子的右边界和右儿子的左边界,如果两个格子相邻并且可以连通,那么就把这两个格子所在的连通块合并,如果合并之前两个连通块都有建筑,那么就把 ans a n s 1 1
查询时区间的合并类似。
复杂度 O(NMlogN)
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define p2 p << 1
#define p3 p << 1 | 1
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
inline char get() {
    char c; while ((c = getchar()) != 'O' && c != '-'
        && c != '|' && c != '+' && c != '.' && c != 'Q' && c != 'C'); return c;
}
const int N = 1e5 + 5, M = 26, L = N << 2;
int n, m, q, to[M], vis[M], tfa[M];
char s[N][M]; bool tis[M];
bool orz0(char c) {return c == 'O' || c == '-' || c == '+';}
bool orz1(char c) {return c == 'O' || c == '|' || c == '+';}
struct cyx {
    int cnt, fa[M]; bool is[M];
    int cx(int x) {if (fa[x] != x) fa[x] = cx(fa[x]); return fa[x];}
    bool zm(int x, int y) {
        int ix = cx(x), iy = cx(y);
        if (ix != iy) {
            bool res = is[ix] && is[iy];
            fa[iy] = ix; is[ix] |= is[iy]; is[iy] = 0; return res;
        }
        return 0;
    }
    void init(int r) {
        int i; cnt = 0; For (i, 1, m << 1) is[fa[i] = i] = 0;
        For (i, 1, m) if (s[r][i] == 'O') is[i] = is[i + m] = 1;
        For (i, 1, m) zm(i, i + m); For (i, 1, m - 1)
            if (orz0(s[r][i]) && orz0(s[r][i + 1])) zm(i, i + 1);
        For (i, 1, m << 1) if (fa[i] == i && is[i]) cnt++;
    }
} T[L];
cyx mer(cyx a, cyx b, int mid) {
    int i, x; cyx res; res.cnt = a.cnt + b.cnt;
    For (i, 1, m) {
        res.fa[i] = a.fa[i] <= m ? a.fa[i] : a.fa[i] + m;
        res.fa[i + m] = b.fa[i + m] > m ? b.fa[i + m] : b.fa[i + m] + m * 3;
        res.fa[i + (m << 1)] = a.fa[i + m] > m ? a.fa[i + m] + m : a.fa[i + m];
        res.fa[i + m * 3] = b.fa[i] <= m ? b.fa[i] + m * 3 : b.fa[i];
        res.is[i] = a.is[i]; res.is[i + m] = b.is[i + m];
        res.is[i + (m << 1)] = a.is[i + m]; res.is[i + m * 3] = b.is[i];
    }
    For (i, 1, m) if (orz1(s[mid][i]) && orz1(s[mid + 1][i]) &&
            res.zm(i + (m << 1), i + m * 3)) res.cnt--;
    For (i, 1, m << 2) vis[i] = 0, to[i] = 0;
    For (i, 1, m << 1) x = res.cx(i), vis[x] = res.is[x] ? 1 : -1, to[x] = i;
    For (i, 1, m << 1) tfa[i] = to[res.cx(i)], tis[i] = 0;
    For (i, 1, m << 2) if (vis[i]) tis[to[i]] = vis[i] == 1;
    For (i, 1, m << 1) res.fa[i] = tfa[i], res.is[i] = tis[i]; return res;
}
void build(int l, int r, int p) {
    if (l == r) return T[p].init(l);
    int mid = l + r >> 1; build(l, mid, p2); build(mid + 1, r, p3);
    T[p] = mer(T[p2], T[p3], mid);
}
void update(int l, int r, int pos, int p) {
    if (l == r) return T[p].init(pos);
    int mid = l + r >> 1; if (pos <= mid) update(l, mid, pos, p2);
    else update(mid + 1, r, pos, p3); T[p] = mer(T[p2], T[p3], mid);
}
cyx query(int l, int r, int s, int e, int p) {
    if (l == s && r == e) return T[p]; int mid = l + r >> 1;
    if (e <= mid) return query(l, mid, s, e, p2);
    else if (s >= mid + 1) return query(mid + 1, r, s, e, p3);
    else return mer(query(l, mid, s, mid, p2),
        query(mid + 1, r, mid + 1, e, p3), mid);
}
int main() {
    int i, j, x, y; char c; n = read(); m = read();
    For (i, 1, n) For (j, 1, m) s[i][j] = get(); build(1, n, 1);
    q = read(); while (q--) {
        c = get(); x = read(); y = read();
        if (c == 'C') s[x][y] = get(), update(1, n, x, 1);
        else printf("%d\n", query(1, n, x, y, 1).cnt);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值