POJ - 3417 Network(LCA + DP)

题目大意:给出一棵N个结点的无根树,现在要在上面加上M条边,问,有多少种破坏方式(破坏一条树边,一条新边),能使这张新图变成至少两部分

解题思路:首先,假设添加的边为(u,v),那么u – > lca(u,v) –> v – >u就形成了一个环了,也就是说,每条添加的边都会在树上形成一个环

本来树上的每条边都是一条桥的,由于加了新的边了,形成了连通分量了,使得边的性质发生了些变化

首先,树边在0个连通分量里面的,那表示该树边还是桥,破坏了它,图肯定能分成两个部分,所以破坏方式就有M种了

接着,树边在1个连通分量里面的,破坏了该树边的话,并不能使图变成两个部分,但可以再破坏一下和他相关的新边,新边一被破坏,那么图肯定能分成至少两个部分了,所以破坏方式有1种

最后,树边在2个或者2个连通分量,那破坏那条树边和和他相关的新边,并没什么卵用,这并不会使图分裂(这个你可以画两个矩形,两个矩形共用一条边,你会发现破话公共边后再破坏别的边,并不会使图变成两份)

那怎么知道树边在几个连通分量里面?
这里有一个技巧,我们假设dp[i]为父结点指向i的这条边(树边)被覆盖的次数,被覆盖了几次,就在几个连通分量里面了
假设新边为(u,v),u和v的LCA为lca,那么就dp[u]++, dp[v]++, dp[lca] -= 2
为什么要这么做了,因为我们后面会从下面往上更新,从叶结点更新到根结点,那样是不是u–>lca–>根的所有的边的覆盖次数都被增加了dp[u],所有v –>lca–>根的所有边的覆盖次数都被增加了dp[v],但因为我们给了dp[lca] -= 2,所以lca–>根的更新都被抵消掉了

#include <cstdio>
#include <cstring>

#define N 100010
#define M 200010

struct Edge{
    int from, to, next;
}E[M];

struct Query{
    int from, to, lca, next;
}Q[M];

int head[N], head_Query[N], f[N], dp[N], num[N];
int n, m, tot;
bool vis[N];

void AddEdge(int u, int v) {
    E[tot].from = u; E[tot].to = v; E[tot].next = head[u]; head[u] = tot++;
    u = u ^ v; v = u ^ v; u = u ^ v;
    E[tot].from = u; E[tot].to = v; E[tot].next = head[u]; head[u] = tot++;
}
void AddEdge_Query(int u, int v) {
    Q[tot].from = u; Q[tot].to  = v; Q[tot].next = head_Query[u]; head_Query[u] = tot++;
    u = u ^ v; v = u ^ v; u = u ^ v;
    Q[tot].from = u; Q[tot].to  = v; Q[tot].next = head_Query[u]; head_Query[u] = tot++;
}

void init() {
    memset(head, -1, sizeof(head));
    tot = 0;

    int u, v;
    for (int i = 1; i < n; i++) {
        scanf("%d%d", &u, &v);
        AddEdge(u, v);
    }

    memset(head_Query, -1, sizeof(head_Query));
    memset(dp, 0, sizeof(dp));
    tot = 0;

    for (int i = 0; i < m; i++) {
        scanf("%d%d", &u, &v);
        AddEdge_Query(u, v);
        dp[u]++; dp[v]++;
    }
}

int find(int x) {
    return x == f[x] ? x : f[x] = find(f[x]);
}

void Tarjan(int u) {
    vis[u] = true; f[u] = u;
    for (int i = head[u]; ~i; i = E[i].next) {
        int v = E[i].to;
        if (!vis[v]) {
            Tarjan(v);
            f[v] = u;
        }
    }

    for (int i = head_Query[u]; ~i; i = Q[i].next) {
        int v = Q[i].to;
        if (vis[v]) 
            Q[i].lca = find(v);
    }
}

int dfs(int u) {
    vis[u] = true;
    for (int i = head[u]; ~i; i = E[i].next) {
        int v = E[i].to;
        if (!vis[v]) {
            int t = dfs(v);
            num[++tot] = t;
            dp[u] += t;
        }
    }
    return dp[u];
}

void solve() {
    memset(vis, 0, sizeof(vis));
    Tarjan(1);
    for (int i = 0; i < tot; i += 2)
        dp[Q[i].lca] -= 2;

    memset(num, 0, sizeof(num));
    memset(vis, 0, sizeof(vis));
    tot = 0;
    dfs(1);
    int ans = 0;
    for (int i = 1; i <= tot; i++)
        if (num[i] == 1)
            ans++;
        else if (num[i] == 0)
            ans += m;
    printf("%d\n", ans);
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        init();
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值