1. 程式人生 > >清北學堂模擬賽d3t6 c

清北學堂模擬賽d3t6 c

font return mil sca str clas 刪掉 種類 ios

技術分享

分析:比較神奇的一道題.要把樹變成環肯定要先變成鏈,然後把鏈給拼接成環.接下來考慮一個腦洞大開的樹形dp:設f[i][0]表示i不與父節點相連的鏈數,f[i][1]表示i與父節點相連的鏈數,先考慮怎麽轉移f[i][0],如果i不與父節點相連,那麽i肯定與兩個子節點相連,其它的子節點都不與父節點相連,而且要剪掉與父親節點的一條邊,所以f[i][0] = (Σf[j][0]) - f[p][0] - f[q][0] + f[p][1] + f[q][1] - 1.f[i][1]也能很容易推導出來f[i][1] = (Σf[j][0]) - f[p][0] + f[p][1].這兩個式子中的p,q使我們選出來與i組成鏈的子節點,為了使得f[i][0/1]最小,我們要選出使f[j][1] - f[j][0]最小的p,q,這個在枚舉的時候掃一下就可以了.

最後是合並,一個樹有N-1條邊,先不斷地刪邊,然後加邊,加到N-1條邊,最後再補一條邊形成一個環,可以發現刪邊和加邊是對稱的,需要刪掉鏈-1條邊,那麽也需要加上鏈-1條邊,最後用一條邊形成一個環就可以了.

樹形dp,考慮好鏈的種類和怎麽從子節點轉移,充分利用好加邊和刪邊的對稱性,就能A掉此題,最關鍵的還是狀態的表示,樹形dp可能會需要保存不同的狀態,如果對於當前狀態推不下去了,就多加點狀態,直到可做為止.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using
namespace std; const int inf = 0x7fffffff; int n, head[100010], to[200010], nextt[200010], tot = 1, f[100010][2]; void add(int x, int y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void dfs(int u, int from) { int min1 = inf, min2 = inf,son = 0,sum = 0,sum2 = 0; for (int i = head[u]; i; i = nextt[i]) {
int v = to[i]; if (v != from) { dfs(v, u); son++; sum += f[v][0]; sum2 += f[v][1]; int temp = f[v][1] - f[v][0]; if (temp < min1) { min2 = min1; min1 = temp; } else if (temp < min2) min2 = temp; } } if (son == 0) f[u][0] = f[u][1] = 1; if (son == 1) f[u][0] = f[u][1] = sum2; else if (son >= 2) { f[u][0] = sum + min1 + min2 - 1; f[u][1] = sum + min1; } } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); add(u, v); add(v, u); } dfs(1, 0); printf("%d\n", f[1][0] * 2 - 1); return 0; }

清北學堂模擬賽d3t6 c