---
# System prepended metadata

title: Chữa bài DP trên cây 28/06/2023

---

**Chủ đề là DP trên cây nên các bạn đừng làm tham nhé 😑**

Các bài quy ước $1$ là gốc cây.

## Bài 1: [Tô màu cây](https://marisaoj.com/problem/230)
- Trạng thái DP: $f(u, i)$ là số lượng đỉnh đen lớn nhất khi tô hết cây con gốc $u$ và $u$ có màu $i$. Quy ước $0$ là màu trắng còn $1$ là màu đen.
- Cập nhật:
  + $f(u,0)=\sum{max(f(v,1), f(v,0))}$ với $v$ là con trực tiếp của $u$. Do $u$ màu trắng nên các con $v$ có thể màu trắng hoặc đen.
  + $f(u,1)=\sum{f(v,0)}$, do $u$ đen nên các con $v$ chỉ có thể là màu trắng.

- Đáp án là $max(f(1,0/1))$.

Code
:::spoiler
```c++
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e5;

vector<int> adj[MAXN+5];
int dp[MAXN+5][2];

void dfs(int u, int p) {
    dp[u][0] = 0;
    dp[u][1] = 1;
    for (int v : adj[u]) {
        if (v == p) continue;
        dfs(v, u);
        dp[u][0] += max(dp[v][0], dp[v][1]);
        dp[u][1] += dp[v][0];
    }
}

int main() {
    int n;
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(1, 0);
    cout << max(dp[1][0], dp[1][1]) << endl;
    return 0;
}
```
:::

## Bài 2: [Tô màu cây 2](https://marisaoj.com/problem/232)
- Trạng thái DP: $f(u,i)$ là số lượng cách tô màu khi tô hết cây con gốc $u$ và $u$ có màu $i$. $i$ từ $0$ tói $2$
- Cập nhật:
  + $f(u,0)=\prod{(f(v,1) + f(v,2))}$, do với mỗi đỉnh $v$, ta có thể tô hoặc là màu $1$ hoặc là màu $2$.
  + Tương tự với các màu còn lại.
- Đáp án $f(1,1)+f(1,2)+f(1,3)$.

Code:
:::spoiler
```c++
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int MOD = 1e9 + 7;
const int MAXN = 1e5;

vector<int> adj[MAXN + 1];
int dp[MAXN + 1][3];

void dfs(int u, int p) {
    dp[u][0] = dp[u][1] = dp[u][2] = 1;
    for (int v : adj[u]) {
        if (v == p) continue;
        dfs(v, u);
        dp[u][0] = (dp[u][0] * (dp[v][1] + dp[v][2])) % MOD;
        dp[u][1] = (dp[u][1] * (dp[v][0] + dp[v][2])) % MOD;
        dp[u][2] = (dp[u][2] * (dp[v][0] + dp[v][1])) % MOD;
    }
}

signed main() {
    int n;
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(1, 0);
    int ans = (dp[1][0] + dp[1][1] + dp[1][2]) % MOD;
    cout << ans << endl;
    return 0;
}
```
:::

## Bài 3: [Đường đi có tổng lớn nhất](https://marisaoj.com/problem/229)
- Trước tiên, đương đi đơn có 2 dạng như sau, một số bạn chỉ tính dạng 1 thì sai:
![](https://hackmd.io/_uploads/ByivVhtun.png)

- Nhưng trước tiên, cứ tính dạng 1 trước với trạng thái $f(u)$ là giá trị đường đi có tổng lớn nhất bắt đầu ở $u$.
- Cập nhật:
  + $f(u)=max(0,max(f(v))+a_u$. Có thể nối $u$ với một đường đi bắt đầu ở $v$ nào đó, hoặc không bắt đầu một đường đi mới chỉ có mỗi $u$.
- Để xử lí trường hợp đường đi thứ 2, với mọi $u$, chọn ra $v$ và $v'$ là con trực tiếp của $u$ sao cho $f(v)$ và $f(v')$ lớn nhất, nối đường đi từ $v$ lên $u$ xuống $v'$.
- Đáp án là $max(f(u))$.

Code
:::spoiler
```c++
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e5;
#define int long long

vector<int> adj[MAXN+5];
int A[MAXN+5];
int dp[MAXN+5];
int ans = LLONG_MIN;

void dfs(int u, int p) {
    dp[u] = A[u];
    vector<int> f;
    for (int v : adj[u]) {
        if (v == p) continue;
        dfs(v, u);
        dp[u] = max(dp[u], A[u] + dp[v]);
        f.push_back(dp[v]);
    }

    sort(f.begin(), f.end(), greater<int>());
    if(f.size() > 1) ans = max(ans, A[u] + f[0]+ f[1]);
}


signed main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> A[i];
    }
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(1, 0);
    for (int i = 1; i <= n; i++) {
        ans = max(ans, dp[i]);
    }
    cout << ans << endl;
    return 0;
}
```
:::

## Bài 4: [Đường đi độ dài k](https://marisaoj.com/problem/231)
- Tương tự bài trên, trạng thái DP để tính đường đi loại 1: $f(u,i)$, số lượng đường đi bắt đầu ở $u$ và có độ dài $i$.
- Cập nhật:
  + $f(u,i)=\sum{f(v,i-1)}$.
- Với đường đi loại 2, ví dụ ta nối một đường đi từ $v$ lên $u$, từ $u$ sẽ phải xuống một đỉnh $v'$ khác. Nếu nối một đường đi độ dài $p$ từ $v$ lên $u$, thì $v'$ phải có độ dài $k - p - 1$. Nghĩa là tính tổng:
$f(v,p) \times f(v',k-p-1)$ với $v'$ khác $v$.

Code
:::spoiler
```c++
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int MAXN = 1e5;
const int MAXK = 100;

vector<int> adj[MAXN+5];
int dp[MAXN+5][MAXK+5];
int n, k;
int ans = 0;

void dfs(int u, int p) {
    dp[u][0] = 1;
    for (int v : adj[u]) {
        if (v == p) continue;
        dfs(v, u);
        for (int i = 1; i <= k; i++) {
            dp[u][i] += dp[v][i-1];
        }
    }

    // for(int i = 1; i <= k; i++) cout << dp[u][i] << ' ';
    // cout << endl;
    
    int t = 0;
    for (int v : adj[u]) {
        if(v == p) continue;
        for(int i = 1; i < k; i++){
            t += dp[v][i - 1] * (dp[u][k - i] - dp[v][k - i - 1]);
        }
    }
    ans += t / 2;
    //cout << ans << endl;
}

signed main() {
    cin >> n >> k;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(3, 0);
   // cout << ans << endl;
    for (int i = 1; i <= n; i++) {
        ans += dp[i][k];
    }
    cout << ans << endl;
    return 0;
}
```
:::