在树上,我们可以任选一个节点作为根节点,从而定义出每个节点的深度和每棵子树的根。
在 DP 的状态表示中,第一维通常是节点编号,第二维代表状态基  。大多数时候,我们采用 递归  的方式实现树形动态规划。对于每一个节点x x x 回溯时,从子节点向节点x x x  。
有根树,有点权。m m m S S S ∈ S \in S ∈ S ∈ S \in S ∈ S S S S 
1 2 3 4 5 6 7 8 9 10 11 12 int  n, m, w[N], dp[N][M];auto  dfs = [&](this  auto && dfs, int  u) -> void  {    dp[u][1 ] = w[u];     for  (auto & v : E[u]) {         dfs (v);         for  (int  k = m; k >= 1 ; --k) {             for  (int  i = k; i <= m; i++)                 dp[u][i] = max (dp[u][i], dp[u][k] + dp[v][i - k]);           }     } }; printf (dp[st][m]);
有根树,有点权。最大化子树点权和。
1 2 3 4 5 6 7 8 9 10 11 int  n, w[N], dp[N];int  ans = -0x3f3f3f3f ;auto  dfs = [&](this  auto && dfs, int  u) -> void  {    dp[u] = w[u];     for  (auto & v : E[u]) {         dfs (v);         dp[u] = max (dp[u] + dp[v], dp[u]);     }     ans = max (ans, dp[u]); }; print (ans);
可 任选一点为根 ,尝试将问题化为子树上的问题。
无根树,点集S S S S S S S S S 
可任选一点为根,则点集S S S ∈ S \in S ∈ S ∉ S \not\in S  ∈ S 
状态表示:f [ u , j ] f[u,j] f [ u , j ] u u u j = 0 j=0 j = 0 u u u j = 1 j=1 j = 1 u u u 
状态转移:v v v u u u 
若j = 0 j=0 j = 0 f [ u , 0 ] = f [ u , 0 ] + max  { f [ v , 0 ] , f [ v , 1 ] } f[u,0] = f[u,0]+\max\{f[v,0],f[v,1]\} f [ u , 0 ] = f [ u , 0 ] + max { f [ v , 0 ] , f [ v , 1 ] }  若j = 1 j=1 j = 1 f [ u , 1 ] = f [ u , 1 ] + f [ v , 0 ] f[u,1]=f[u,1]+f[v,0] f [ u , 1 ] = f [ u , 1 ] + f [ v , 0 ]  1 2 3 4 5 6 7 8 9 10 int  n, w[N], dp[N][2 ];auto  dfs = [&](this  auto && dfs, int  u) -> void  {    dp[u][1 ] = w[u];     for  (auto & v : E[u]) {         dfs (v);         dp[u][0 ] += max (dp[v][0 ], dp[v][1 ]);         dp[u][1 ] += dp[v][0 ];     } }; print (max (dp[rt][0 ], dp[rt][1 ]));
树。点集S S S E u v E_{uv} E u v  u ∈ S ∨ v ∈ S u\in S \lor v\in S u ∈ S ∨ v ∈ S S S S 
1 2 3 4 5 6 7 8 9 10 int  n, dp[N][2 ];auto  dfs = [&](this  auto && dfs, int  u) -> void  {    dp[u][0 ] = 0 , dp[u][1 ] = 1 ;     for  (auto  v : E[u]) {         dfs (v);         dp[u][0 ] += dp[v][1 ];         dp[u][1 ] += min (dp[v][1 ], dp[v][0 ]);     } }; print (min (dp[rt][0 ], dp[rt][1 ]));
无根树。求包含u u u 
Input Output 6 12 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 vector<int > E[N]; Z f[N];      Z g[N];      auto  dfs1 = [&](this  auto && dfs, int  u, int  p) -> void  {    f[u] = 1 ;     for  (auto & v : E[u]) {         if  (v == p) continue ;         dfs1 (v, u);         f[u] *= f[v] + 1 ;     } } auto  dfs2 = [&](this  auto && dfs, int  u, int  p) -> void  {    for  (auto & v : E[u]) {         if  (v == p) continue ;         g[v] = g[u] / (f[v] + 1 ) * f[v] + f[v];         dfs2 (v, u);     } } dfs1 (1 , 0 );g[1 ] = f[1 ], dfs2 (1 , 0 ); print (g);
题意  给树上每个点染色,颜色编号c i c_{i} c i  ∑ i = 1 n a i c i \sum\limits_{i=1}^{n}a_i c_i i = 1 ∑ n  a i  c i  The Omnipotent Monster Killer )
思路  状态表示:f u , j f_{u,j} f u , j  u u u j j j u u u ∑ i ∈ subtree(u)  n a i c i \sum\limits_{i\in \operatorname{subtree(u)}}^{n}a_i c_i i ∈ s u b t r e e ( u ) ∑ n  a i  c i  
状态转移:设v v v u u u f u , j ← ∑ min  k ≠ j { f v , k } f_{u,j}\leftarrow \sum\min\limits_{k\neq j}\{f_{v,k}\} f u , j  ← ∑ k  = j min  { f v , k  } 
可以发现总操作次数不会很多,因为即使最笨的办法,给未染色的节点二染色,然后选择权值较大的部分染色也可以让未染色的节点数减少一半。所以每次至少可以让未染色的节点的总权值减少一半,总权值数量级1 0 12 × 3 × 1 0 5 = 3 × 1 0 17 10^{12}\times3\times10^{5}= 3\times10^{17} 1 0 1 2 × 3 × 1 0 5 = 3 × 1 0 1 7 
时间复杂度Θ ( n log  2 v ) \Theta(n\log^{2} v) Θ ( n log  2 v ) v v v 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 constexpr  int  N = 3e5  + 8 , K = 60 ;vector<int > E[N]; ll w[N], dp[N][K]; auto  dfs = [&](this  auto && dfs, int  u, int  p) -> void  {    for  (int  i = 1 ; i < K; i++) dp[u][i] = i * w[u];     for  (auto & v : E[u]) {         if  (v == p) continue ;         dfs (v, u);         for  (int  i = 1 ; i < K; i++) {             ll mn = 8e18 ;             for  (int  j = 1 ; j < K; ++j) {                 if  (i != j) mn = min (mn, dp[v][j]);             }             dp[u][i] += mn;         }     } } void  eachT ()           ll ans = 8e18 ;     for  (int  i = 1 ; i < K; i++) {         ans = min (ans, dp[1 ][i]);     }     printf ("%lld\n" , ans); } 
前后缀和最小值优化  时间复杂度Θ ( n log  v ) \Theta(n\log v) Θ ( n log  v ) v v v 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 constexpr  int  N = 3e5  + 8 , K = 60 ;vector<int > E[N]; ll w[N], dp[N][K]; ll pre[N], suf[N]; auto  dfs = [&](this  auto && dfs, int  u, int  p) -> void  {    for  (int  i = 1 ; i < K; i++) dp[u][i] = i * w[u];     pre[0 ] = 1e18 , suf[K] = 1e18 ;     for  (auto & v : E[u]) {         if  (v == p) continue ;         dfs (v, u);         for  (int  i = 1 ; i < K; i++) pre[i] = min (dp[v][i], pre[i - 1 ]);         for  (int  i = K - 1 ; i; --i) suf[i] = min (dp[v][i], suf[i + 1 ]);         for  (int  i = 1 ; i < K; i++) {             dp[u][i] += min (pre[i - 1 ], suf[i + 1 ]);         }     } } 
状态表示优化  时间复杂度Θ ( n log  v ) \Theta(n\log v) Θ ( n log  v ) Θ ( n ) \Theta(n) Θ ( n ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 constexpr  int  N = 3e5  + 8 ;vector<int > E[N]; ll w[N]; ll dp1[N], dp2[N];     int  t[N];             auto  dfs = [&](this  auto && dfs, int  u, int  p) -> void  {    int  cnt = E[u].size () - (p != 0 );       ll sum = 0 ;                   vector<ll> d (cnt + 3 )  ;        for  (auto & v : E[u]) {         if  (v == p) continue ;         dfs (v, u);         sum += dp1[v];         if  (t[v] < cnt + 3 ) {             d[t[v]] += dp2[v] - dp1[v];           }     }     for  (int  i = 1 ; i <= cnt + 2 ; i++) {           ll res = sum + d[i] + i * w[u];         if  (res < dp1[u]) {               dp2[u] = dp1[u];             dp1[u] = res;             t[u] = i;         }         else  if  (res < dp2[u]) {               dp2[u] = res;         }     } } void  eachT ()           printf ("%lld\n" , dp1[1 ]); } 
给定一棵n n n 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int  n;cin >> n; vector<int > w (n) , p (n) , vis (n)  ;for  (int  i = 0 ; i < n; i++) {    cin >> w[i]; } for  (int  i = 1 ; i < n; i++) {    int  u = w[read ()], v = w[read ()];     E[u].push_back (v);     E[v].push_back (u); } vector<Z> f (n)  ;   auto  dfs = [&](this  auto && dfs, int  u) -> void  {    f[u] = 1 ;     for  (auto & v : E[u]) {         if  (v == p[u]) continue ;         p[v] = u;         dfs (v);         f[u] *= f[v] + 1 ;     } }; dfs (0 );vis[0 ] = 1 ; Z res = f[0 ], sum = res; for  (int  i = 2 ; i <= n; i++) {    int  u = i;     if  (!vis[u]) {         vector<int > stk;         while  (!vis[u]) {             stk.push_back (u);             u = p[u];         }         while  (!stk.empty ()) {             int  v = stk.back (); stk.pop_back ();             sum = sum / (f[v] + 1 ) * f[v];             vis[v] = 1 ;         }     }     res += sum; }; cout << res << '\n' ; 
引理 (拓扑序计数)给定一棵树,在点上写一个排列,使得每个点的点权比其父亲的数大的方案数,这等价于,满足每个节点的父亲排在他前面的排列方案数为n ! ∏ i = 0 n − 1 s i \dfrac{n!}{\prod_{i=0}^{n-1} s_{i}} ∏ i = 0 n − 1  s i  n !  
题意  求满足p i = i p_{i}=i p i  = i n ⩽ 5000 n \leqslant 5000 n ⩽ 5 0 0 0 
思路  树形 DP,常见的f p u ← f u f_{p_{u}}\leftarrow f_{u} f p u   ← f u  f u ← f p u f_{u}\leftarrow f_{p_{u}} f u  ← f p u   f u f_{u} f u  u u u 
对于本题,设 键 f u , i f_{u,i} f u , i  u u u 在u \boldsymbol{u} u i \boldsymbol{i} i   ,值  是 方案数 。
思考如何转移f u ← f p u f_{u}\leftarrow f_{p_{u}} f u  ← f p u   f u , j ← f p u , i f_{u,j}\leftarrow f_{p_{u},i} f u , j  ← f p u  , i  p u p_{u} p u  i i i u u u j j j p u p_{u} p u  u u u 
两个问题:哪些位置?什么顺序?
空位:由于u u u u u u u u u s u − 1 s_{u}-1 s u  − 1 C  n − j − 1 s u − 1 \operatorname{C}_{n-j-1}^{s_{u-1}} C n − j − 1 s u − 1   u u u p u p_{u} p u  s p u − 1 s_{p_{u}}-1 s p u   − 1 u u u s u s_{u} s u  s p u − 1 − s u s_{p_{u}}-1-s_{u} s p u   − 1 − s u  a = C  n − i − 1 − s u s p u − 1 − s u a=\operatorname{C}_{n-i-1-s_{u}}^{s_{p_{u}}-1-s_{u}} a = C n − i − 1 − s u  s p u   − 1 − s u   
顺序:需要计算u u u v v v s v ! ∏ i ∈ s v s i \dfrac{s_{v}!}{\prod_{i\in s_{v}} s_{i}} ∏ i ∈ s v   s i  s v  !  
b = ∏ v s v ! ∏ i ∈ s v s i = ( s p u − s u ) ! ( ∏ i ∈ s p u s i ) / ( ∏ i ∈ s u s i ) b=\prod_{v}\dfrac{s_{v}!}{\prod_{i\in s_{v}} s_{i}}=\dfrac{(s_{p_{u}}-s_{u})!}{\big(\prod_{i\in s_{p_{u}}} s_{i}\big)/\big(\prod_{i\in s_{u}} s_{i}\big)} b = v ∏  ∏ i ∈ s v   s i  s v  !  = ( ∏ i ∈ s p u    s i  ) / ( ∏ i ∈ s u   s i  ) ( s p u   − s u  ) !  
这两个式子相乘a b ab a b Θ ( n 2 ) \Theta(n^{2}) Θ ( n 2 ) Θ ( n ) \Theta(n) Θ ( n ) 
现在得到的不是答案,因为f u , i f_{u,i} f u , i  u u u 
所以拓扑序中u u u i i i g u , i = c d f u , i g_{u,i}=cdf_{u,i} g u , i  = c d f u , i  
总的时空复杂度Θ ( n 2 ) \Theta(n^{2}) Θ ( n 2 ) 
(没用取模类,代码很丑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #include  <bits/stdc++.h>  using  namespace  std;using  ll = long  long ;constexpr  int  mod = 998244353 ;constexpr  int  N = 5e3  + 8 ;ll fac[N], ifac[N]; ll qpow (ll a, ll n)   {	ll res = 1 ; 	while  (n) { 		if  (n & 1 ) res = res * a % mod; 		a = a * a % mod; 		n >>= 1 ; 	} 	return  res; } ll inv (ll n)   {	return  qpow (n, mod - 2 ); } void  init (int  n)  	fac[0 ] = ifac[0 ] = 1 ; 	for  (int  i = 1 ; i <= n; i++) { 		fac[i] = fac[i - 1 ] * i % mod; 	} 	ifac[n] = inv (fac[n]); 	for  (int  i = n; i > 0 ; i--) { 		ifac[i - 1 ] = ifac[i] * i % mod; 	} } ll C (int  n, int  m)   {	if  (n < m || m < 0 ) return  0 ; 	return  fac[n] * ifac[m] % mod * ifac[n - m] % mod; } int  main ()  	int  n; 	cin >> n; 	init (n); 	vector p (n, -1 )  ;  	for  (int  i = 1 ; i < n; i++) { 		cin >> p[i]; 		p[i]--; 	} 	vector siz (n, 1LL )  ;  	for  (int  i = n - 1 ; i; i--) { 		siz[p[i]] += siz[i]; 	} 	auto  psiz = siz;   	for  (int  i = n - 1 ; i; i--) { 		psiz[p[i]] *= psiz[i]; 		psiz[p[i]] %= mod; 	} 	vector dp (n, vector<ll>(n))  ; 	dp[0 ][0 ] = 1 ; 	for  (int  i = 1 ; i < n; i++) { 		vector<ll> pre (n)  ; 		for  (int  j = 0 ; j < n - 1 ; j++) { 			ll tmp = dp[p[i]][j] * C (n - 1  - j - siz[i], siz[p[i]] - 1  - siz[i]) % mod; 			(pre[j + 1 ] += pre[j] + tmp) %= mod; 		} 		ll tmp = fac[siz[p[i]] - siz[i] - 1 ] * inv (psiz[p[i]]) % mod * psiz[i] % mod * siz[p[i]] % mod; 		for  (int  j = 1 ; j < n; j++) { 			(dp[i][j] += pre[j] * tmp) %= mod; 		} 	} 	for  (int  i = 0 ; i < n; i++) { 		cout << (dp[i][i] * C (n - 1  - i, siz[i] - 1 ) % mod * fac[siz[i]] % mod * inv (psiz[i]) % mod) << " \n" [i == n - 1 ]; 	} 	return  0 ; } 
2024 杭电多校 7009 - 关于 agKc 实在不喜欢自动化于是啥都自己合成这件事
[[…/contests/2024 杭电多校 /2024-08-05:2024“钉耙编程”中国大学生算法设计超级联赛(7)|2024-08-05:2024“钉耙编程”中国大学生算法设计超级联赛(7)]]
方程为f u = ∑ w u v f v f_{u}=\sum w_{uv}f_{v} f u  = ∑ w u v  f v  f 1 > 1 0 9 f_{1}>10^{9} f 1  > 1 0 9 > 1 0 9 >10^{9} > 1 0 9 f f f 1 0 9 + 1 10^{9}+1 1 0 9 + 1 
初始化要在 eachT 的最开始而不是最后,因为提前 return 会错过初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ll mx = 0 ; vector<vector<pair<ll, int >>> E (n); vector<ll> f (n)  ;auto  dfs = [&](this  auto && dfs, int  u) -> void  {    for  (auto & [w, v] : E[u]) {         dfs (v);         f[u] += w * f[v];         if  (f[u] >= INF) {             f[u] = INF;             break ;         }     } }; int  ok = 1 ;for  (auto & [w, v] : E[rt]) {    dfs (v);     f[rt] += w * f[v];     mx = max (mx, w * f[v]);     if  (f[rt] - mx > INF) {         ok = 0 ;         break ;     } }; if  (ok) printf ("%lld\n" , ww[rt] - mx);else  printf ("Impossible\n" );
树。求一个节点,使得以这个节点为根时,所有节点的深度之和最大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 vector<int > siz (n) , dep (n) , dp (n)  ;auto  dfs = [&](this  auto && dfs, int  u, int  p) -> void  {    siz[u] = 1 ;     dp[u] = dep[u] = dep[p] + 1 ;     for  (auto  v : E[u]) {         if  (v == p) continue ;         dfs (v, u);         siz[u] += siz[v];         dp[u] += dp[v];     } }; dfs (0 , -1 );int  imx = 0 ;auto  dfs2 = [&](this  auto && dfs, int  u, int  p) -> void  {    for  (auto  v : E[u]) {         if  (v == p) continue ;         dp[v] = dp[u] - (siz[v]) + (n - siz[v]);         dfs2 (v, u);     }     if  (dp[u] >= dp[imx]) imx = u; }; dfs2 (0 , -1 );print (imx);
求以每个点为根时树的高度,这用换根 DP 可以在 $\Theta(n)$ 的时间内求得。点u u u = max  ( u = \max(u = max ( u u u u ) ) ) 
点u u u = max  ( u = \max(u = max ( u ) + 1 )+1 ) + 1 
如果点u u u u u u = max  ( u = \max(u = max ( u ) + 1 )+1 ) + 1 u u u = = = + 1 +1 + 1 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 vector<int > p (n, -1 ) , up (n) , d (n) , d2 (n) , id (n, -1 )  ;auto  dfs1 = [&](this  auto && dfs, int  u) -> void  {    d[u] = 1 ;     for  (auto  v : E[u]) {         if  (v == p[u]) continue ;         p[v] = u;         dfs (v);         if  (d[v] + 1  >= d[u]) {             d2[u] = d[u];             d[u] = d[v] + 1 ;             id[u] = v;         } else  if  (d[v] + 1  >= d2[u]) {             d2[u] = d[v] + 1 ;         }     } }; dfs1 (0 );vector<int > dp (n)  ;auto  dfs2 = [&](this  auto && dfs, int  u) -> void  {    for  (auto  v : E[u]) {         if  (v == p[u]) continue ;         if  (v == id[u]) {             up[v] = max (up[u] + 1 , d2[u] + 1 );         } else  {             up[v] = dp[u] + 1 ;         }         dp[v] = max (d[v], up[v]);         dfs (v);     } }; dp[0 ] = d[0 ]; dfs2 (0 );