1. 程式人生 > >poj3417(lca + 樹形dp)

poj3417(lca + 樹形dp)

con pre 方案 當前 樹狀數組 刪除 技術分享 splay tdi

題目鏈接: 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

poj3417(lca + 樹形dp)