Luogu P3698 [CQOI2017]小Q的棋盘 (树形DP)

https://www.luogu.com.cn/problem/P3698
image

  • f[u][j] 表示在以u为根节点的子树中分配多少步能走到的最多的点数,树上背包那样转移,但这样显然是不对的,跑到另一棵子树时候还要往回走而从消耗步数
  • 影响每棵树的可分配步数以及遍历子树情况的是是否回到根节点
  • f[u][j][1/0] 表示在以u为根节点的子树中分配多少步是/否回到根节点能走到的最多的点数
    u不回到根节点,v可回可不回。
    v不回:因为v一定是从u过来的所以这种情况一定是从u回根转移过来的。(如果v是子树中第一个转移的子节点,则1/0没有影响)
    v回:它可以从f[u][j - t][0]也可以从f[u][j - t][1]转移过来,为什么可以从f[u][j - t][0]转移过来?不是要先从之前节点出来回到根节点吗? 其实因为背包是没有顺序的,只要看作先走的回根节点的v,不回的是从回的转移过来,这样不回的答案也不会相互叠加。
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false) ,cin.tie(0), cout.tie(0);
//#pragma GCC optimize(3,"Ofast","inline")
#define ll long long

#define PII pair<ll, ll>
const int N = 1e2 + 5;
const int M = 1e6 + 5;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double PI = acos(-1.0);
int h[N], e[N << 1], ne[N << 1], idx;
int n, m;
int f[N][N][2];
void add ( int a, int b ) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs( int u, int fa ) {
    f[u][0][0] = f[u][0][1] = 1;
    for ( int i = h[u]; ~i; i = ne[i] ) {
        int v = e[i]; if( v == fa ) continue;
        dfs(v, u);
        for ( int j = m; j >= 1; -- j ) {
            for ( int t = 1; t <= j; ++ t ) {
                f[u][j][0] = max(f[u][j][0], f[u][j - t][1] + f[v][t - 1][0]);
                if(t >= 2) {
                    f[u][j][0] = max(f[u][j][0], f[u][j - t][0] + f[v][t - 2][1]);
                    f[u][j][1] = max(f[u][j][1], f[u][j - t][1] + f[v][t - 2][1]);
                }
            }
        }
    } 

}
int main () {
    IOS
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for ( int i = 1; i <= n - 1; ++ i ) {
        int u, v; cin >> u >> v; ++ u, ++ v;
        add(u, v); add(v, u);
    }
    dfs(1, -1);
    int ans = 0;
    for (int i = 0; i <= m; i++)
    ans = max(ans, f[1][i][0]);
    cout << ans << endl;
    return 0;
}
posted @ 2022-04-04 10:02  qingyanng  阅读(19)  评论(0编辑  收藏  举报