HDU 6203 贪心 + LCA + dfs序 + BIT

7 篇文章 0 订阅
6 篇文章 0 订阅

简要题意:给出一个 n+1 个点的树,以及若干个点对,需要断开一些点,使得这些点对路径不连通。输出应该断开的最少点数。

我们断开一个点,能够影响到的是:
1. 子树中过这个点的路径.
2. 一个点在子树中,另一个点在祖先中的路径。

为了使得以上两个影响尽可能的大,我们每次需要使得断开的点的子树尽可能大。
因此当我们打算断开一对点对 (u,v) 的时候,为了使得断开的点的影响尽可能大,我们需要断开路径上深度最小的那个点,也就是 lca(u,v)

①. 如果一对点对 (u,v) 的其中一个点在某个被断开的点的子树中,而另一个点不在,则说明这两点间的路径经过被断开点,那么这条路径就没有再次断开的必要了。
②. 如果一对点对两个点都不在任何已经被断开的点的子树中,则说明这两个点的 lca 还需要被断开。
事实上还有一种情况, 如果两个点都被覆盖了,则可能出现的情况是,两个点之间的路径都被覆盖了,但是却没有一个点被断开。对于这种情况我们也需要将两点间的 lca 断开。但是我们断开这次 lca 之后,有可能会导致之前我们处理过的,本该被判定为情况①,因为处理顺序的缘故被判定为情况②,从而导致结果偏大。因此为了避免这种情况,需要离线询问,按 lca 的深度从大到小来处理。

判定一个点是否在某个被断开的点的子树中,只需要看这个点是否被标记过。
每次断开一个点,都需要把这个点的子树里的点都标记上。对于这种对子树的简单操作,一般我们都使用dfs序,更麻烦一些的,使用树链剖分。
使用了dfs序之后,给子树标记可以使用线段树。不过我们只需要知道一个点是否被标记过,因此使用树状数组即可。

#include <bits/stdc++.h>

using namespace std;

const int maxn = 110000;

int p[maxn][20], deep[maxn], L[maxn], R[maxn], cid;
vector<int> G[maxn];

void dfs(int u, int fa) {
    p[u][0] = fa;
    L[u] = ++cid;
    deep[u] = deep[fa] + 1;
    for(auto v : G[u]) {
        if(v == fa) continue;
        dfs(v, u);
    }
    R[u] = cid;
}

int goup(int x, int len) {
    for(int i = 0; i < 20; i++) {
        if(len & (1 << i) && x)
            x = p[x][i];
    }
    return x;
}

int lca(int u, int v) {
    if(deep[u] < deep[v]) swap(u, v);
    int d = deep[u] - deep[v];
    u = goup(u, d);
    if(v == u) return u;
    for(int 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];
}

namespace BIT {
    int C[maxn];
    void init() {
        memset(C, 0, sizeof C);
    }
    int lowbit(int x) {
        return x & -x;
    }
    void ins(int x, int v) {
        for(int i = x; i < maxn; i+=lowbit(i))
            C[i] += v;
    }
    int ask(int x) {
        int res = 0;
        for(int i = x; i; i-=lowbit(i))
            res += C[i];
        return res;
    }
}

struct A {
    int u, v, lc, lcdep;
    bool operator < (const A & b) const {
        return lcdep > b.lcdep;
    }
};

void init() {
    for(int i = 0; i < maxn; i++) G[i].clear();
    deep[0] = 0;
    cid = 0;
}

int main() {
    int n;
    while(~scanf("%d", &n)) {
        init();
        for(int i = 1; i <= n; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            x ++, y++;
            G[x].push_back(y);
            G[y].push_back(x);
        }
        n++;
        dfs(1, 0);
        for(int i = 1; i < 20; i++) {
            for(int j = 1; j <= n; j++) {
                if(p[j][i-1] == 0) p[j][i] = 0;
                else p[j][i] = p[p[j][i-1]][i-1];
            }
        }
        int q;
        scanf("%d", &q);
        vector<A> V;
        while(q--) {
            int x, y;
            scanf("%d%d", &x, &y);
            x++, y++;
            int lc = lca(x, y);
            V.push_back({x, y, lc, deep[lc]});
        }
        sort(V.begin(), V.end());
        int ans = 0;
        BIT::init();
        for(int i = 0; i < V.size(); i++) {
            int u = V[i].u, v = V[i].v, lc = V[i].lc;
            if(BIT::ask(L[u]) || BIT::ask(L[v])) continue;
            BIT::ins(L[lc], 1);BIT::ins(R[lc] + 1, -1); ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值