比較容易想到的一種做法是:設dp[i][j] 表示 i 為根,選 j 個點的答案。在子樹合併時,枚舉子樹選取的點的個數和當前選取的結點個數,加上統計當前這條邊對答案的貢獻更新。
轉移式子為:dp[u][i+j] = min(dp[u][i+j],dp[u][i]+dp[v][j] + i * j * w)
一跑會發現樣例都過不了,原因是這條邊的貢獻只在選的點超過乙個時才會計算,答案必然會漏算某些貢獻。
乙個補救的方法是:另開乙個陣列tp[i][j] 記錄dp[i][j] 取最小值時,選的點到當前i點的距離之和。
轉移方程為:dp[u][i+j] =min(dp[u][i+j],dp[u][i] + dp[v][j] + tp[u][i] * j + tp[v][j] * i + i * j * w
要注意在 dp[u][i+j] 相同的情況下,tp[u][i+j] 要取最小值,因為dp[u][i+j]相同的情況下,顯然tp[u][i]越小對後面的轉移越有利。
一看樣例過了,似乎是一種可行的方案。交上去發現只有30分(還不如暴力打滿40)。
原因是dp轉移式子中,轉移值不只取決於 dp[i][j],還涉及到tp[i][j],而 tp[i][j] 只在 dp[i][j] 盡量小的前提下盡量小。這相當於是在忽略tp[i][j]在轉移中帶來的影響,資料足夠強的話很容易卡掉。
正確的做法是:
還是從貢獻的角度考慮,乙個很明確的思路是,如果在子樹中確定好選的點,產生的新貢獻和當前這條邊權有關。觀察到題目一定有解,並且我們只關注最後的答案。考慮直接用樹形dp維護對最終答案的貢獻。
dp[i][j] 表示 i 為根選 j個結點 對最終答案的貢獻。顯然dp[1][k] 會是正確的答案。
轉移方程為:dp[u][i+j] = min(dp[u][i+j],dp[u][i] + dp[v][j] + w * j * (k - j))
當決定在子樹v中選 j 個點時,最終答案連向這顆子樹的邊權一定會經過 j * (k - j) 次
細算一下複雜度上界是5e8,需要在轉移時加一些剪枝降低上界。
**:
#include
using
namespace std;
const
int maxn =
1e5+10;
#define pii pair
#define fir first
#define sec second
typedef
long
long ll;
vector g[maxn]
;int vis[maxn]
,sum[maxn]
,m,n,k;
ll dp[maxn]
[300
],tmp[
1000];
void
dfs(
int u,
int fa)
dp[u][0
]=0;
if(vis[u]
) sum[u]=1
,dp[u][1
]=0;
for(
auto it : g[u])}
sum[u]
+= sum[it.fir]
;for
(int i =
0; i <=
min(k,sum[u]
); i++)}
}int
main()
for(
int i =
1,u,v,w; i < n; i++
)dfs(1
,0);
printf
("%lld\n"
,dp[1]
[k])
;return0;
}/*5 3 2
1 3 5
1 2 4
1 3 5
1 4 3
4 5 1
11 6 2
2 5 7 8 9 10
1 2 2
1 3 4
2 4 3
2 5 6
3 6 8
3 7 11
4 8 9
6 9 2
6 10 1
7 11 4
*/
CCF 201909 5 城市規劃 樹形dp
題目鏈結 題目大意 思路 u是v的直連父親,先往下搜,向上回溯時,列舉邊計算貢獻,即u和v之間邊w,v裡面選了p個,則all v這一塊選k p個 邊w被經過p k p 次,實際轉移時,考慮v裡取了p個,u在已經搜過的子樹里取了q個,以此來更新dp u p q 的值 dp u p 表示在u這棵子樹 含...
城市規劃道路樹1 3 1版
使用方法 選擇所要合併的樹物件,執行指令碼 說明 將隨機樹高度的指令碼寫到了合併樹的函式中 並且在第一次執行合併樹的指令碼時 執行隨機樹高度的操作 而接下來的合併樹操作中 不再執行 有效的提高了效率 經過測試合併1萬個物件 可以在三分鐘左右完成 因為還不知道maxscript中的time如何操作 沒...
bzoj 3456 城市規劃
題意 求n個點的無向連通圖個數 n個點不同,答案對1004535809取模 n 130000 題解 生成函式的種種神奇應用 不過這玩意真是越來越不oi了 笑 這道題首先考慮遞推公式 設f x 為結點數為x的答案 那麼用總的無向圖數減去不連通的無向圖數目就是答案 f i 2 i i 1 2 f j 2...