poj3417(lca + 树形dp)
Posted ygeloutingyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 }
以上是关于poj3417(lca + 树形dp)的主要内容,如果未能解决你的问题,请参考以下文章