【学习笔记/习题总结】kruskal重构树

kruskal 重构树

注:默认您学会了求最小生成树的 kruskal 算法,并且知道何为最小瓶颈生成树和最小瓶颈路。

定义:

在跑 kruskal 的过程中我们会从小到大加入若干条边,我们仍然按照这个顺序加边,并且视这条边为一个节点,点权为加入边的边权。

一次加边会合并两个集合,我们分别以这两个集合的根节点为新建点的左、右儿子,然后将两个集合和新点合并为一个集合,将新建点设为根。

在进行 \(n - 1\) 轮加边后,我们就得到了一棵恰有 \(n\) 个叶子的二叉树,同时每个非叶子节点恰有两个儿子。这棵树就是 kruskal 重构树。

举个例子,这张图:

建出 kruskal 重构树是:

点上括号里的数表示点权。

实现:

其实和 kruskal 求最小生成树的实现一样,对所有边排序,依次考虑这条边可不可以被加入,通过并查集维护两点之间的联通性,没了。

struct Union_Set{
    int fa[MAXN];

    void init(int n){
        for(register int i = 1; i <= n; i++) fa[i] = i;
    }

    int Find(int x){
        return x == fa[x] ? x : fa[x] = Find(fa[x]);
    }
}U; //并查集

void Kruskal(){
    U.init(n << 1); //初始化并查集
    int tot = 0; sum = n; //tot 是加进去了多少边,sum 是重构树上点的数量
    sort(line + 1, line + 1 + m, cmp);

    for(register int i = 1; i <= m; i++){
        int u = line[i].from, v = line[i].to;
        int fa_u = U.Find(u), fa_v = U.Find(v);

        if(fa_u != fa_v){
            ++tot;
            val[++sum] = line[i].dis;
            Add(sum, fa_u), Add(fa_u, sum);
            Add(sum, fa_v), Add(fa_v, sum);
        }
        if(tot == n - 1) break; //加进 n - 1 条边一定能构成树
    }
}

性质:

由于 kruskal 重构树是一棵二叉树,并且是依次加边,所以它有一些美妙的性质:

如果我们将叶子节点的权值视为 \(0\),则整棵树满足堆结构。所以任意两点间路径的最小权值的最大值即为它们 \(LCA\) 的点权。

也就是说,到点 \(u\) 的简单路径上最大边权的最小值 \(≤ val\) 的所有点 \(y\) 均在 kruskal 重构树上的某一颗子树内,且恰为这棵子树中的叶子结点。

题:

P4768 [NOI2018] 归程

\(\textit{Description}\)

\(N\) 个节点 \(M\) 条边的无向图,用 \(l\)\(a\) 表示长度,海拔。
\(Q\) 次询问,给定起点 \(v\),水位线 \(p\),只能经过海拔不低于 \(p\) 的边,求能到达的点中距离 \(1\) 号节点最近的距离。

\(\textit{Solution}\)

首先跑一遍单源最短路,预处理出每个点到 \(1\) 号节点的最短路径,注意到这是归程,所以 spfa 会被卡,所以要用 dijstra。然后建出关于海拔 \(a\) 的 kruskal 重构树,要关于海拔 \(a\) 降序排序使得 kruskal 重构树满足大根堆性质,那么能到达的点是重构树中一个子树的所有叶子节点。然后我们去找节点 \(v\) 到根的路径上最浅的海拔不低于 \(p\) 的点,这一步可以倍增处理。然后以该节点为根的子树中的叶子结点到 \(1\) 节点的最短路径就是答案,直接 dfs 时预处理就行,不要像写这篇博的傻逼一样把树拍扁了再在 dfs 序上线段树处理最小值。

\(\textit{Code}\)

Code
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN = 2e5 + 10, MAXM = 4e5 + 10, SIZE = 25;
const int INF = 2147483647;
int t, n, m, q, k, s, cnt, sum, num, last;
int head[MAXN << 1], dis[MAXN], data[MAXN];
int val[MAXN << 1], lpos[MAXN << 1], rpos[MAXN << 1];
int fa[MAXN << 1][SIZE];
bool vis[MAXN];

struct Line{
    int from, to, dis;
}line[MAXM];

inline bool cmp(const Line &a, const Line &b){
    return a.dis > b.dis;
}

struct Edge{
    int to, next, dis;
}e[MAXM << 1];

inline void Add(int u, int v, int w){
    e[++cnt].to = v;
    e[cnt].dis = w;
    e[cnt].next = head[u];
    head[u] = cnt;
}

struct Road{
    int dis, pos;

    bool operator > (const Road &a) const{
        return dis > a.dis;
    }
};

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

void Dijkstra(int u){
    priority_queue< Road, vector<Road>, greater<Road> > q;
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));

    dis[u] = 0;
    q.push((Road){0, u});
    while(!q.empty()){
        Road t = q.top(); q.pop();

        if(vis[t.pos]) continue;
        vis[t.pos] = true;

        for(register int i = head[t.pos]; i; i = e[i].next){
            int v = e[i].to;
            if(dis[v] > dis[t.pos] + e[i].dis){
                dis[v] = dis[t.pos] + e[i].dis;
				if(!vis[v]) q.push((Road){dis[v], v});
            }
        }
    }
}

struct Union_Set{
    int fa[MAXN << 1];

    void init(int n){
        for(register int i = 1; i <= n; i++) fa[i] = i;
    }

    int Find(int x){
        return x == fa[x] ? x : fa[x] = Find(fa[x]);
    }
}U;

void Kruscal(){
    int tot = 0; cnt = 0;
    memset(head, 0, sizeof(head));

    U.init(n << 1);
    sort(line + 1, line + 1 + m, cmp);

    for(register int i = 1; i <= m; i++){
        int u = line[i].from, v = line[i].to;
        int fa_u = U.Find(u), fa_v = U.Find(v);

        if(fa_u != fa_v){
            ++tot;
            val[++sum] = line[i].dis;
            Add(fa_u, sum, 0), Add(sum, fa_u, 0);
            Add(fa_v, sum, 0), Add(sum, fa_v, 0);
            U.fa[fa_u] = U.fa[fa_v] = sum;
        }
        if(tot == n - 1) break;
    }
}

void dfs(int rt, int father){
    lpos[rt] = num + 1;
    fa[rt][0] = father;
    for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1];

    int son = 0;
    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(v == father) continue;
        ++son, dfs(v, rt);
    }
    if(!son) data[++num] = dis[rt];

    rpos[rt] = num;
}

struct Segment_Tree{
    struct Tree{
        int l, r;
        int min;
    }tr[MAXN << 2];

    inline int lson(int rt){
        return rt << 1;
    }

    inline int rson(int rt){
        return rt << 1 | 1;
    }

    void Pushup(int rt){
        tr[rt].min = min(tr[lson(rt)].min, tr[rson(rt)].min);
    }

    void Build(int rt, int l, int r){
        tr[rt].l = l, tr[rt].r = r;
        if(l == r){
            tr[rt].min = data[l];
            return;
        }

        int mid = (l + r) >> 1;
        Build(lson(rt), l, mid);
        Build(rson(rt), mid + 1, r);

        Pushup(rt);
    }

    int Query_Min(int rt, int l, int r){
        if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].min;

        int ans = INF;
        int mid = (tr[rt].l + tr[rt].r) >> 1;
        if(l <= mid) ans = min(ans, Query_Min(lson(rt), l, r));
        if(r > mid) ans = min(ans, Query_Min(rson(rt), l, r));

        return ans;
    }
}S;

int Query(int v, int p){
    for(register int i = 24; i >= 0; i--)
        if(fa[v][i] && val[fa[v][i]] > p) v = fa[v][i];

    return S.Query_Min(1, lpos[v], rpos[v]);
}

void Clear(){
    cnt = num = sum = last = 0;
    memset(fa, 0, sizeof(fa));
    memset(head, 0, sizeof(head));
}

int main(){
    t = read();
    while(t--){
        Clear();

        n = read(), m = read(), sum = n;
        for(register int i = 1; i <= m; i++){
            int u, v, w, p;
            u = read(), v = read(), w = read(), p = read();
            Add(u, v, w), Add(v, u, w);
            line[i] = (Line){u, v, p};
        }

        Dijkstra(1);
        Kruscal();
        dfs(sum, 0);
        S.Build(1, 1, n);

        q = read(), k = read(), s = read();
        for(register int i = 1; i <= q; i++){
            int v, p;
            v = (read() + k * last - 1) % n + 1, p = (read() + k * last) % (s + 1);
            last = Query(v, p);
            printf("%d\n", last);
        }
    }

    return 0;
}

P1967 [NOIP2013] 货车运输

\(\textit{Description}\)

给定一个 \(N\) 个点 \(M\) 条边的无向图,每条边有一个权值,\(Q\) 次询问,求 \(u\)\(v\) 两点路径上最大的权值的最小值。

\(\textit{Solution}\)

权值的限制可以想到 kruskal 重构树,求最大权值的最小值显然要按照最大生成树建重构树,\(LCA(u, v)\) 的权值即为 \(u\)\(v\) 路径上的最大权值的最小值。

\(\textit{Code}\)

纯口胡,没写过,写的两个 \(\log\) 的树剖。

P3280 [SCOI2013] 摩托车交易

\(\textit{Description}\)

写了就没意思了。

\(\textit{Solution}\)

先说结论:题目里两个限制都是假的。
对于要求最后要卖光:
我们只需要尽可能多的携带黄金,不用在意是否能卖光。因为实际情况下我们可以调整购入黄金的量来达到要求。
对于不能丢弃:
我们能买黄金就尽量买,在路途中丢弃黄金和购入时购入恰好的量是等效的。

一个城市有车站的话就向上一个车站连一条边权为 \(inf\) 的边,反正最后能缀在一起就行了。

然后按照最大生成树建重构树,求两点之间的 \(LCA\),用个变量记录一下当前携带的黄金数量模拟就行了。

\(\textit{Code}\)

纯口胡,没写过,写的两个 \(\log\) 的树剖。

P4197 Peaks

\(\textit{Description}\)

给定一个无向图,有 \(Q\) 个询问,求从点 \(v\) 出发,只经过权值小于等于 \(x\) 的边能到达的所有点中第 \(k\) 大的权值。

\(\textit{Solution}\)

kruskal 重构树和主席树缝起来。

按照最小生成树建出重构树,倍增找到点 \(v\) 的祖先中最浅的权值小于等于 \(x\) 的点 \(u\),能到达的所有点就是以 \(u\) 为根的子树的叶子节点。

我们可以通过树剖等知识知道,一棵子树内的所有点的 dfs 序是一个连续的区间。把重构树拍扁,注意只有叶子结点存的是原图的点权,所以每个非叶子结点只需要记录它包含的叶子节点的区间就行了,然后建主席树,查询区间第 \(k\) 大即可。

\(\textit{Code}\)

Code
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10, MAXM = 5e5 + 10, SIZE = 25;
int n, m, q, cnt, len, lim, sum, num;
int head[MAXN << 1], hig[MAXN], data[MAXN];
int val[MAXN << 1], lpos[MAXN << 1], rpos[MAXN << 1];
int root[MAXN];
int fa[MAXN << 1][SIZE];

struct Line{
    int from, to, dis;
}line[MAXM];

inline bool cmp(const Line &a, const Line &b){
    return a.dis < b.dis;
}

struct Edge{
    int to, next;
}e[MAXM << 1];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

struct Union_Set{
    int fa[MAXN << 1];

    void init(int n){
        for(register int i = 1; i <= n; i++) fa[i] = i;
    }

    int Find(int x){
        return x == fa[x] ? x : fa[x] = Find(fa[x]);
    }
}U;

struct Chairman_Tree{
    int tot;
    struct Tree{
        int lson, rson;
        int size;
    }tr[MAXN * 50];

    #define lson(x) tr[x].lson
    #define rson(x) tr[x].rson

    void Build(int &rt, int l, int r){
        rt = ++tot;
        if(l == r) return;

        int mid = (l + r) >> 1;
        Build(lson(rt), l, mid);
        Build(rson(rt), mid + 1, r);
    }

    void Update(int &rt, int last, int pos, int L, int R){
        rt = ++tot;
        tr[rt].lson = tr[last].lson;
        tr[rt].rson = tr[last].rson;
        tr[rt].size = tr[last].size + 1;

        if(L == R) return;

        int mid = (L + R) >> 1;
        if(pos <= mid) Update(lson(rt), lson(last), pos, L, mid);
        else Update(rson(rt), rson(last), pos, mid + 1, R);
    }

    int Query_Kth(int rt_l, int rt_r, int k, int L, int R){
        if(tr[rt_r].size - tr[rt_l].size < k) return -1;
        if(L == R) return L;

        int mid = (L + R) >> 1;
        int cnt = tr[lson(rt_r)].size - tr[lson(rt_l)].size;
        if(k <= cnt) return Query_Kth(lson(rt_l), lson(rt_r), k, L, mid);
        else return Query_Kth(rson(rt_l), rson(rt_r), k - cnt, mid + 1, R);
    }
}C;

void Kruscal(){
    int tot = 0;
    U.init(n << 1);
    sort(line + 1, line + 1 + m, cmp);

    for(register int i = 1; i <= m; i++){
        int u = line[i].from, v = line[i].to;
        int fa_u = U.Find(u), fa_v = U.Find(v);

        if(fa_u != fa_v){
            ++tot;
            val[++sum] = line[i].dis;
            U.fa[fa_u] = U.fa[fa_v] = sum;
            Add(sum, fa_u), Add(fa_u, sum);
            Add(sum, fa_v), Add(fa_v, sum);
        }
        if(tot == n - 1) break;
    }
}

void dfs(int rt, int father){
    lpos[rt] = num;
    fa[rt][0] = father;
    for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1];

    int son = 0;
    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(v == father) continue;

        ++son;
        dfs(v, rt);
    }
    if(!son){
        ++num;
        C.Update(root[num], root[num - 1], hig[rt], 1, lim);
    }

    rpos[rt] = num;
}   

int Query(int v, int x, int k){
    for(register int i = 24; i >= 0; i--)
        if(fa[v][i] && val[fa[v][i]] <= x) v = fa[v][i];
    k = rpos[v] - lpos[v] - k + 1;

    if(k < 1) return -1;
    return C.Query_Kth(root[lpos[v]], root[rpos[v]], k, 1, lim);
}

int main(){
    n = read(), m = read(), q = read(), sum = n;
    for(register int i = 1; i <= n; i++)
        hig[i] = read(), data[i] = hig[i];

    sort(data + 1, data + 1 + n);
    len = unique(data + 1, data + 1 + n) - data - 1;
    for(register int i = 1; i <= n; i++){
        int pos = lower_bound(data + 1, data + 1 + len, hig[i]) - data;
        hig[i] = pos, lim = max(lim, pos);
    }

    for(register int i = 1; i <= m; i++){
        int u, v, w;
        u = read(), v = read(), w = read();
        line[i] = (Line){u, v, w};
    }

    Kruscal();
    C.Build(root[0], 1, lim);
    dfs(sum, 0);

    for(register int i = 1; i <= q; i++){
        int v, x, k, ans;
        v = read(), x = read(), k = read();
        ans = Query(v, x, k);
        if(ans != -1) ans = data[ans];

        printf("%d\n", ans);
    }

    return 0;
}

P7834 [ONTAK2010] Peaks 加强版

只加强了一个强制在线,然而 kruskal 重构树就是在线做法。

[AGC002D] Stamp Rally

\(\textit{Description}\)

给定一张无向图,每次询问从 \(x\)\(y\) 分别出发,一共要经过 \(z\) 个点(经过相同的点只算一次),使得走过编号最大的边最小。

\(\textit{Solution}\)

以边的编号为权值,按照最小生成树建重构树,每次二分需要走过的最大的编号,让 \(x\)\(y\) 分别跳到最浅的满足要求的祖先。则以该节点为根的子树中的叶子结点都是可以到达的,判断两节点包含的叶子结点的数量是否大于等于 \(z\) 即可。

\(\textit{Code}\)

Code
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10, SIZE = 25;
int n, m, q, cnt, sum, maxn;
int head[MAXN << 1], val[MAXN << 1], siz[MAXN << 1];
int fa[MAXN << 1][SIZE];

struct Line{
    int from, to, dis;
}line[MAXN];

bool cmp(const Line &a, const Line &b){
    return a.dis < b.dis;
}

struct Edge{
    int to, next;
}e[MAXN << 2];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

struct Union_Set{
    int fa[MAXN << 1];

    void init(int n){
        for(register int i = 1; i <= n; i++) fa[i] = i;
    }

    int Find(int x){
        return fa[x] == x ? x : fa[x] = Find(fa[x]);
    }
}U;

void Kruscal(){
    int tot = 0;
    U.init(n << 1);
    sort(line + 1, line + 1 + m, cmp);

    for(register int i = 1; i <= m; i++){
        int u = line[i].from, v = line[i].to;
        int fa_u = U.Find(u), fa_v = U.Find(v);

        if(fa_u != fa_v){
            ++tot;
            val[++sum] = line[i].dis;
            maxn = max(maxn, val[sum]);
            U.fa[fa_u] = U.fa[fa_v] = sum;
            Add(sum, fa_u), Add(fa_u, sum);
            Add(sum, fa_v), Add(fa_v, sum);
        }
    }
}

void dfs(int rt, int father){
    fa[rt][0] = father;
    for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1];

    int son = 0;
    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(v == father) continue;

        ++son, dfs(v, rt);
        siz[rt] += siz[v];
    }
    if(!son) ++siz[rt];
}

bool Check(int x, int y, int num, int tot){
    for(register int i = 24; i >= 0; i--)
        if(fa[x][i] && val[fa[x][i]] <= num) x = fa[x][i];
    for(register int i = 24; i >= 0; i--)
        if(fa[y][i] && val[fa[y][i]] <= num) y = fa[y][i];

    if(x == y) return (siz[x] >= tot);
    else return (siz[x] + siz[y] >= tot);
}

int Query(int x, int y, int z){
    int l = 1, r = maxn, ans = 0;

    while(l <= r){
        int mid = (l + r) >> 1;
        if(Check(x, y, mid, z)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }

    return ans;
}

int main(){
    n = read(), m = read(), sum = n;
    for(register int i = 1; i <= m; i++){
        int u, v, w;
        u = read(), v = read(), w = i;
        line[i] = (Line){u, v, w};
    }

    Kruscal();
    dfs(sum, 0);

    q = read();
    for(register int i = 1; i <= q; i++){
        int x, y, z;
        x = read(), y = read(), z = read();
        printf("%d\n", Query(x, y, z));
    }

    return 0;
}

P3684 [CERC2016]机棚障碍 Hangar Hurdles

\(\textit{Description}\)

太长,不想写了。

\(\textit{Soltion}\)

对于每个位置求出它能通过的最大的矩形大小,转化成求最大瓶颈路问题。

求最大矩形大小可以二分查找。记录一个二维前缀和 \(sum_{i,j}\),遇到障碍物加 \(1\),设当前点为 \((x, y)\),二分 \(len\),使得\(sum_{x - len, y - len}\)\(sum_{x + len, y + len}\) 的和为 \(0\),最后最大的矩形就是 \(len \times 2 + 1\)

然后去连边,边权是两端点能通过的最大的矩形的最小值。从当前点分别向它的右边和下边连边即可。注意的是障碍物也需要连边,否则一些不能通达的点不会出现在重构树里,查询时会寄掉。有障碍物只需要把边权设为 \(0\) 即可。

按照最大生成树去建重构树,询问就是求两点之间的 \(LCA\),如果 \(LCA\) 的权值是 \(0\) 的话说明不能通达,判掉即可。

\(\textit{Code}\)

Code
#include<cstdio>
#include<algorithm>

#define X first
#define Y second
#define Pair pair< int, int >
#define Make(x, y) make_pair(x, y)

using namespace std;

const int MAXN = 1010, MAXM = 1e6 + 10, SIZE = 35;
int n, m, q, cnt, all, tot, teg;
int lim[MAXM], head[MAXM << 1], val[MAXM << 1];
int fa[MAXM << 1], son[MAXM << 1], deep[MAXM << 1], size[MAXM << 1];
int top[MAXM << 1], dfn[MAXM << 1];
int num[MAXN][MAXN], sum[MAXN][MAXN];
char map[MAXN][MAXN];

struct Edge{
    int to, next;
}e[MAXM << 2];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

struct Line{
    int from, to, dis;
}line[MAXM << 2];

bool cmp(const Line &a, const Line &b){
    return a.dis > b.dis;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

bool Check(int x, int y, int len){
    int lx = x - len, ly = y - len;
    int rx = x + len, ry = y + len;
    return sum[rx][ry] - sum[lx - 1][ry] - sum[rx][ly - 1] + sum[lx - 1][ly - 1] == 0;
}

int Lower_Bound(int x, int y){
    int l = 0, r = min(min(x - 1, n - x), min(y - 1, n - y));
    int ans = 0;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(Check(x, y, mid)) ans = mid, l = mid + 1;
        else r = mid - 1;
    }

    return ans;
}

struct Union_Set{
    int fa[MAXM << 1];

    void init(int n){
        for(register int i = 1; i <= n; i++) fa[i] = i;
    }

    int Find(int x){
        return x == fa[x] ? x : fa[x] = Find(fa[x]);
    }
}U;

void Kruscal(){
    int num = 0; all = tot;
    U.init(tot << 1);
    sort(line + 1, line + 1 + m, cmp);

    for(register int i = 1; i <= m; i++){
        int u = line[i].from, v = line[i].to, w = line[i].dis;
        int fa_u = U.Find(u), fa_v = U.Find(v);
        if(fa_u != fa_v){
            ++num;
            val[++all] = w;
            U.fa[fa_u] = U.fa[fa_v] = all;
            Add(all, fa_u), Add(fa_u, all);
            Add(all, fa_v), Add(fa_v, all);
        }
        if(num == tot - 1) break;
    }
}

void dfs_deep(int rt, int father, int depth){
	size[rt] = 1;
	fa[rt] = father;
	deep[rt] = depth;
	
	int max_son = -1;
	for(register int i = head[rt]; i; i = e[i].next){
		int v = e[i].to;
		if(v == father) continue;
		
		dfs_deep(v, rt, depth + 1);
		size[rt] += size[v];

		if(size[v] > max_son){
			son[rt] = v;
			max_son = size[v];
		}
	}
}

void dfs_top(int rt, int top_fa){
	dfn[rt] = ++teg;
	top[rt] = top_fa;

	if(!son[rt]) return;
	dfs_top(son[rt], top_fa);
	
	for(register int i = head[rt]; i; i = e[i].next){
		int v = e[i].to;
		if(!dfn[v]) dfs_top(v, v);
	}
}

int Get_LCA(int x, int y){
    while(top[x] != top[y]){
        if(deep[top[x]] < deep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }
    if(deep[x] > deep[y]) swap(x, y);

    return x;
}

int main(){
    n = read();
    for(register int i = 1; i <= n; i++){
        scanf("%s", map[i] + 1);
        for(register int j = 1; j <= n; j++){
            num[i][j] = ++tot;
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
            if(map[i][j] == '#') ++sum[i][j];
        }
    }

    for(register int i = 1; i <= n; i++){
        for(register int j = 1; j <= n; j++){
            if(map[i][j] == '.'){
                int len = Lower_Bound(i, j);
                lim[num[i][j]] = len * 2 + 1;
            }
            else lim[num[i][j]] = 0;
            val[num[i][j]] = lim[num[i][j]];
        }
    }

    for(register int i = 1; i <= n; i++){
        for(register int j = 1; j <= n; j++){
            if(j < n) line[++m] = (Line){num[i][j], num[i][j + 1], min(lim[num[i][j]], lim[num[i][j + 1]])};
            if(i < n) line[++m] = (Line){num[i][j], num[i + 1][j], min(lim[num[i][j]], lim[num[i + 1][j]])};
        }
    }

    Kruscal();
    dfs_deep(all, 0, 1);
    dfs_top(all, all);

    q = read();
    for(register int i = 1; i <= q; i++){
        Pair a, b; int x, y, anc;
        a.X = read(), a.Y = read(), b.X = read(), b.Y = read();
        x = num[a.X][a.Y], y = num[b.X][b.Y];
        anc = Get_LCA(x, y);
        printf("%d\n", val[anc]);
    }

    return 0;
}

还有CF上的两道题,但我找不到了,咕了。

posted @ 2022-11-20 10:57  TSTYFST  阅读(50)  评论(0编辑  收藏  举报