POJ 3417 Network (LCA + DP)

题意:先给出一棵无根树,然后下面再给出m条边,把这m条边连上,然后每次你能毁掉两条边,规定一条是树边,一条是新边,问有多少种方案能使树断裂。

我们知道,这m条边连上后这颗树必将成环,假设新边为(u,v),那么环为u---->LCA(u,v)------->v-------->u,我们给这个环上的边计数1,表示这些边被一个环覆盖了一次。添加了多条新边后,可知树上有些边是会被多次覆盖的,画图很容易发现,但一个树边被覆盖了2次或以上,它就是一条牢固的边,就是说毁掉它再毁掉任何一条新边都好,树都不会断裂,这个结论也是很容易证明的,画图更明显,所以不累述

所以这启发了我们,要统计所有的边被覆盖了几次,我们分情况来讨论

1.覆盖0次,说明这条边不在任何一个环上,这样的边最脆弱,单单是毁掉它就已经可以使树断裂了,这时候只要任意选一条新边去毁,树还是断裂的,所以这样的树边,就产生m种方案(m为新边条数)

2.覆盖1次,说明这条边在一个环上,且,仅在一个环上,那么要使树断裂,就毁掉这条树边,并且毁掉和它对应的那条新边(毁其他的新边无效),就一定能使树断裂,这种树边能产生的方案数为1,一条这样的树边只有唯一解

3.覆盖2次或以上,无论怎么样都不能使树断裂,产生的方案数为0

 

所以,如果我们能知道所有的树边的覆盖,那么统计一次就行了,所以问题只剩下,怎么每条边被覆盖了几次?

需要用到树DP。

首先我们定义dp[u]的意义为,u所对应的那条父边(u和它父亲连接的那条边)被覆盖的次数

对应一条新边(u,v),我们知道是要求LCA(u,v)的,这时候我们计数dp[u]++ , dp[v]++ , dp[lca]-=2

为什么这样计数?我们试着看看,点u和点v和点lca,都试着沿路径一直回到树根处(注意不是回到LCA而是树根),u的路径中每经过一个点,就将这些点上的值加上dp[u],同样v的路径上没经过一个点就将这些点上的值加上dp[v],lca也是这样。你会发现,lca回到树根的部分,其实被抵消掉了,dp值没有变化,而u到lca,v到lca部分的值都已经分别加上了dp[u],dp[v]

这启发了我们,我们在求完所有m对顶点的LCA后,每个u和v都做一次dp[u]++,dp[v]++,dp[lca]-=2,然后我从树根开始向下遍历一次整棵树,在回溯的时候就执行累加dp[u],dp[v]的操作

Network
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 4015 Accepted: 1153

Description

Yixght is a manager of the company called SzqNetwork(SN). Now she's very worried because she has just received a bad news which denotes that DxtNetwork(DN), the SN's business rival, intents to attack the network of SN. More unfortunately, the original network of SN is so weak that we can just treat it as a tree. Formally, there are N nodes in SN's network, N-1 bidirectional channels to connect the nodes, and there always exists a route from any node to another. In order to protect the network from the attack, Yixght builds M new bidirectional channels between some of the nodes.

As the DN's best hacker, you can exactly destory two channels, one in the original network and the other among the M new channels. Now your higher-up wants to know how many ways you can divide the network of SN into at least two parts.

Input

The first line of the input file contains two integers: N (1 ≤ N ≤ 100 000), M (1 ≤ M ≤ 100 000) — the number of the nodes and the number of the new channels.

Following N-1 lines represent the channels in the original network of SN, each pair (a,b) denote that there is a channel between node aand node b.

Following M lines represent the new channels in the network, each pair (a,b) denote that a new channel between node a and node b is added to the network of SN.

Output

Output a single integer — the number of ways to divide the network into at least two parts.

Sample Input

4 1
1 2
2 3
1 4
3 4

Sample Output

3

Source


/*
ID:huang_l1
LANG:C++
PROG:combo
*/
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define prt(k) cout<<#k" = "<<k<<endl;
typedef long long ll;
const int N = 100005;
int head[N], n, m, Q, dep[N], len[N]; /// len[i] 表示 i 到根距离
struct Edge
{
    int to, next, w;
}e[N << 1];
int f[N][22];
int mm;
void add(int u, int v, int w=1)
{
    e[mm] = (Edge){v, head[u], w};
    head[u] = mm++;
}
int cur, cnt;
int p[N], fa[N];
///p[i] -- i 的父亲
int find(int x) { return x==fa[x]? x : fa[x]=find(fa[x]); }
int dp[N];
void init()
{
    mm = cur = 0;
    memset(head, -1, sizeof head);
    dep[1] = 0;
    p[1] = 0;
    memset(dp, 0 , sizeof dp);
}
void dfs(int u, int fa)
{
    f[u][0] = fa;
    dep[u] = dep[fa] + 1;
  ///  dep[u] = dep[p[u]] + 1;
    for (int i=head[u]; ~i; i=e[i].next)
    {
        int v = e[i].to;
        if (v == fa) continue;
        p[v] = u;
        dep[v] = dep[u] + 1;
        dfs(v, u);
    }
}
int maxh;
void gao()
{
    dfs(1, 0);
    int j;
    for (j=1;(1<<j)<n;j++) {
        for (int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
    }
    maxh = j - 1;
}
int swim(int x, int k)
{
    for (int i=maxh;i>=0;i--)
        if (k >> i & 1)
        x = f[x][i];
    return x;
}
int LCA(int x, int y)
{
    if (dep[x] > dep[y]) swap(x, y);
    y = swim(y, dep[y] - dep[x]);
    if (x==y) return x;
    for (int i=maxh;i>=0;i--) {
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}
void Dfs(int u, int fa)
{
    for (int i=head[u];~i;i=e[i].next) {
        int v = e[i].to;
        if (v == fa) continue;
        Dfs(v, u);
        dp[u] += dp[v];
    }
}
int main()
{
    while (cin >> n >> m)
    {
        init();
        for (int i=0;i<n-1;i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            add(u, v); add(v, u);
        }
        gao();
        for (int i=0;i<m;i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            int lca = LCA(u, v);
            dp[u]++, dp[v]++, dp[lca]-=2;
        }
        Dfs(1, 0);
        ll ans = 0;
        for (int i=2;i<=n;i++) {
            if (dp[i]==0) ans += m;
            if (dp[i]==1) ans ++;
        }
        printf("%I64d\n", ans);
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值