poj3417(lca + 树形dp)

题目链接: http://poj.org/problem?id=3417

 

题意: 先给出一棵树, 再添加 m 条新边, 然后再删除其中一条新边和一条树枝, 问有多少中删除方法可以使新树分成两部分 .

思路:  lca + 树形dp

添加新边后必然形成环, 对于添加新边 (u, v), 形成的环为 u -> v -> lca(u, v) -> u . 用一个权值记录一下边被环覆盖的次数, 即给每个环上的所有边权值加一 .

通过画图可以发现:

对于权值为 0 的树枝, 删除后即能将树分成两部分, 即其能和任意一条新边组合成一个合法方案, 对答案贡献为 m;

对于权值为 1 的树枝, 删除后, 只能再删除其对应的新边能将树分成两部分, 即对答案贡献为 1;

对于权值为 2 的树枝, 其和任意新边组合都不能将树分成两部分, 即对答案贡献为 0;

那么现在只需要求出每条树枝的权值即可, 对于这个问题可以通过树形 dp 解决, 用 dp[x] 表示节点 x 和其父亲节点组成的边的权值,

如果直接暴力求权值的话时间复杂度为O(n^2), 对于比较大的数据肯定会 tle . 事实上这些新边增加后形成的环对于彼此的权值是不会有影响的, 那么可以先将所有新边标记一下, 然后从下往上遍历一次树即可计算出所有边的权值.

具体操作为, 对于每条新边 (u, v), 先令 dp[u] += 1, dp[v] += 1, dp[lca] -= 2, 然后 dfs 回溯时将儿子节点的权值累加到父亲节点上即可. (dp[lca] - 2 是为了消除当前环对其上面的节点的影响, 这个过程和树状数组改段求点差不多) .

 

代码:

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include <string.h>
  4 using namespace std;
  5 
  6 const int MAXN = 1e5 + 10;
  7 
  8 struct node{
  9     int u, v, lca, next;
 10 }edge1[MAXN << 1], edge2[MAXN << 1];
 11 
 12 int pre[MAXN], vis[MAXN], dp[MAXN];
 13 int head1[MAXN], head2[MAXN], id1, id2;
 14 
 15 void init(void){
 16     memset(dp, 0, sizeof(dp));
 17     memset(vis, 0, sizeof(vis));
 18     memset(head1, -1, sizeof(head1));
 19     memset(head2, -1, sizeof(head2));
 20     id1 = id2 = 0;
 21 }
 22 
 23 void addedge1(int u, int v){
 24     edge1[id1].v = v;
 25     edge1[id1].next = head1[u];
 26     head1[u] = id1++;
 27 }
 28 
 29 void addedge2(int u, int v){
 30     edge2[id2].v = v;
 31     edge2[id2].next = head2[u];
 32     head2[u] = id2++;
 33 }
 34 
 35 int find(int x){
 36     return pre[x] == x ? x : pre[x] = find(pre[x]);
 37 }
 38 
 39 void jion(int x, int y){
 40     x = find(x);
 41     y = find(y);
 42     if(x != y) pre[x] = y;
 43 }
 44 
 45 void tarjan(int u){
 46     pre[u] = u;
 47     vis[u] = 1;
 48     for(int i = head1[u]; i != -1; i = edge1[i].next){
 49         int v = edge1[i].v;
 50         if(!vis[v]){
 51             tarjan(v);
 52             jion(v, u);
 53         }
 54     }
 55     for(int i = head2[u]; i != -1; i = edge2[i].next){
 56         int v = edge2[i].v;
 57         if(vis[v]) edge2[i].lca = edge2[i ^ 1].lca = find(v);
 58     }
 59 }
 60 
 61 void dfs(int x, int fa){
 62     for(int i = head1[x]; i != -1; i = edge1[i].next){
 63         int v = edge1[i].v;
 64         if(v != fa){
 65             dfs(v, x);
 66             dp[x] += dp[v];
 67         }
 68     }
 69 }
 70 
 71 int main(void){
 72     int n, m, x, y;
 73     while(~scanf("%d%d", &n, &m)){
 74         init();
 75         for(int i = 1; i < n; i++){
 76             scanf("%d%d", &x, &y);
 77             addedge1(x, y);
 78             addedge1(y, x);
 79         }
 80         for(int i = 0; i < m; i++){
 81             scanf("%d%d", &x, &y);
 82             addedge2(x, y);
 83             addedge2(y, x);
 84             dp[x] += 1;
 85             dp[y] += 1;
 86         }
 87         tarjan(1);
 88         for(int i = 0; i < m; i++){
 89             int cc = i << 1;
 90             int lca = edge2[cc].lca;
 91             dp[lca] -= 2;
 92         }
 93         dfs(1, 0);
 94         int ans = 0;
 95         for(int i = 2; i <= n; i++){
 96             if(dp[i] == 0) ans += m;
 97             else if(dp[i] == 1) ans += 1;
 98         }
 99         printf("%d\n", ans);
100     }
101     return 0;
102 }
View Code

 

转载于:https://www.cnblogs.com/geloutingyu/p/7259138.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值