codeforces406D LCA+凸包

6 篇文章 0 订阅
2 篇文章 0 订阅

首先我们来想一下,如果两个山顶间有连边,意味着两座山中间没有任何一座山高于这两座山的连线。
加入从左到右三座山分别是p, q, r,这就意味着Kpq >= Kqr,否则q山将是不合法的,这其实就是在维护一个凸包。
接下来的问题是,我们需要能够到达当前能够到达的最右端的山峰,并且在这里我们能和团队中的另一个人相遇。
第一个问题我们可以反向维护一个下凸壳来解决,之后我们就得到了当前山峰能够到达的最右端的山峰。
那么我们将这两个山峰建边,就成功将原问题转化成了树上的问题。
得到一个树后,第二个问题两个人在一个点相遇也就成了LCA问题。

#include <bits/stdc++.h>
using namespace std;
#define all(x) x.begin(), x.end()
typedef long long LL;

const LL maxn = 1e5+100;

LL n;

struct PoLL {
   LL x, y;
};

vector<PoLL> V;

LL stk[maxn], top = 0;

LL Cross(PoLL a, PoLL b) {
    return a.x*b.y-a.y*b.x;
}

PoLL operator - (PoLL & a, PoLL & b) {
    return {a.x-b.x, a.y-b.y};
}

struct LCA {
    LL p[maxn][20], n, cnt[maxn], deep[maxn];
    vector<LL> G[maxn];
    LL dfs(LL u, LL _p) {
        p[u][0] = _p;
        cnt[u] = 1;
        for(LL i = 0; i < G[u].size(); i++) {
            LL v = G[u][i];
            if(v == _p) continue;
            deep[v] = deep[u] + 1;
            cnt[u] += dfs(v, u);
        }
        return cnt[u];
    }
    void Lca_init(LL _n) {
        n = _n;
        for(LL i = 0; i < maxn; i++) G[i].clear();
        for(LL i = V.size()- 1; i >= 0; i--) {
            if(top <= 1)
                stk[top++] = i;
            else {
                while(top > 1 && Cross(V[stk[top-1]]-V[stk[top-2]], V[i]-V[stk[top-1]]) < 0) {
                    top--;
                }
                stk[top++] = i;
            }
            if(top >= 2) {
                G[1+stk[top-1]].push_back(stk[top-2]+1);
                G[1+stk[top-2]].push_back(stk[top-1]+1);
            }
        }
        deep[n] = 0;
        dfs(n, -1);
        for(LL j = 1; j < 20; j++) {
            for(LL i = 1; i <= n; i++) {
                if(p[i][j-1] == -1) p[i][j] = -1;
                else p[i][j] = p[p[i][j-1]][j-1];
            }
        }
    }
    LL Lca_up(LL u, LL len) {
        for(LL i = 0; i < 20; i++) {
            if(u != -1 && len&(1<<i))
                u=p[u][i];
        }
        return u;
    }
    LL Lca(LL u, LL v) {
        if(deep[u] < deep[v]) swap(u, v);
        u = Lca_up(u, deep[u] - deep[v]);
        if(v == u) return u;
        for(LL i = 19; i >= 0; i--) {
            if(p[u][i] == p[v][i]) continue;
            u = p[u][i];
            v = p[v][i];
        }
        return p[u][0];
    }
} solver;

int main(){
    scanf("%lld", &n);

    for(LL i = 1; i <= n; i++) {
        LL x, y;
        scanf("%lld%lld", &x, &y);
        V.push_back({x, y});
    }
    solver.Lca_init(n);
    LL m;
    scanf("%lld", &m);
    for(LL i = 1; i <= m; i++) {
        LL u, v;
        scanf("%lld%lld", &u, &v);
        printf("%lld ", solver.Lca(u, v));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值