bzoj1956(一個快速簡單的方法)
此題有樹,那麼能用的求最值的演算法有dp,貪心,流等等,如果我們以dp的思路來解題,觀察每顆子樹,那麼每顆子樹都相當於曼哈頓路徑的一部分鏈,子樹根可以在鏈的頭,尾,中間,其中根在頭和尾是等價的。那麼可以設計一個狀態dp[i][0]表示,以i為根的樹組成的一個從根i開始的鏈條,要花費的最小代價,dp[i][1]表示以i為根的樹組成的一個從非根節點開始的鏈條,注意兩個狀態表示的鏈的出點都是非根節點。那麼如果i是葉子節點,dp[i][0]=dp[i][1]=0;
現在考慮轉移:我們現在的目標是把各個子節點代表的鏈條,和根節點,一起串成一個鏈。
如果根節點選擇的子節點的狀態為dp[i][0]那麼不需要新加任何邊,直接用樹邊就好了,如果選擇的是dp[i][1]那麼新加一條邊。
我們現在需要討論的就是到底子節點是選擇用dp[i][0]好,還是選擇用dp[i][1]好,顯然如果我們對於每個子節點都能用dp[i][0]與dp[i][1]中的最小值,而且不加任何邊是最好的,但是由於用了最小值,那麼有可能就會新加一些邊,這樣就不一定好了
轉移的時候就是首先找出根節點的各個子節點的min(dp[子節點][0],dp[子節點][1])的和,再考慮如何將所有這些鏈條串成一個。我們儘量考慮多用樹邊,但是這會和我們前面求和矛盾,因為如果我選了dp[子節點][1]這個鏈條進行組合,是不能用樹邊的不是?那麼我前面的操作都是無用功了?當然不是。注意到,如果dp[子節點][1]<dp[子節點][0]那麼dp[子節點][1]<=dp[子節點][0]-1,也就是說,我們就算多加一條邊,最後得到的結果也是小於等於選擇dp[位元組點][0]來做鏈條的.有了這個性質就很好轉移了
minn表示最小值的和,cnt表示選擇的dp[子節點][0]的個數,size表示總的子節點數。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int first[150200], nextt[300400], too[300400]; int edgetot; void addedge(int a, int b) { too[edgetot] = b; nextt[edgetot] = first[a]; first[a] = edgetot++; too[edgetot] = a; nextt[edgetot] = first[b]; first[b] = edgetot++; } int n; int dp[150200][2]; void com(int i, int &minn, int &cnt) { if (dp[i][0] < dp[i][1]) { minn += dp[i][0]; cnt++; } else { if (dp[i][0] == dp[i][1]) { minn += dp[i][0]; cnt++; } else minn += dp[i][1]; } } void dfs(int now, int fa) { int cnt, minn, size; minn = 0; cnt = 0; size = 0; for (int i = first[now]; i != -1; i = nextt[i]) { int to = too[i]; if (to == fa)continue; dfs(to, now); com(to, minn, cnt); size++; } if (size == 0) { dp[now][0] = dp[now][1] = 0; return; } else { if (cnt >= 1) { dp[now][0] = min(dp[now][0], minn - 1+size); } else { dp[now][0] = min(dp[now][0], minn+size); } if (cnt >= 2) { dp[now][1] = min(dp[now][1], minn + size - 2); } else { if (cnt >= 1) dp[now][1] = min(dp[now][1], minn + size - 1); else dp[now][1] = min(dp[now][1], minn + size); } } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { first[i] = -1; dp[i][0] = dp[i][1] = 1000000000; } for (int i = 0; i < n - 1; i++) { int a, b; scanf("%d%d", &a, &b); addedge(a, b); } dfs(1, 1); printf("%d\n",min(dp[1][0] + 1,dp[1][1]+1)); return 0; }