Access 题解

一、题目:

二、思路:

这道题真的把我迷了一个下午,感谢 zYzYzYzYz 大佬的点拨,让我终于明白了这道题。

注意到如果 \(y\) 的一个祖先是 \(x\),那么先 \(\mathbb{access}(x)\)\(\mathbb{access}(y)\) 本质上相当于只 access 了一次。所以我们可以执行一个从下到上的树形 DP。

注意,以下这个 DP 状态比较复杂。

假设我们现在的目标是求出来 \(x\) 相关的信息,现在轮到了用 \(x\) 的儿子 \(y\) 来去更新 \(x\) 的信息。此时,\(tmpf[k]\) 表示"保证 \(y\) 之前的儿子边都不是实边",操作了 \(k\) 次的方案数;\(tmpg[k]\) 表示“保证 \(y\) 之前的儿子边中恰好有一个是实边”,操作了 \(k\) 次的方案数;\(f[x,k]\) 表示保证 \(y\)\(y\) 之前的儿子边都不是实边,操作了 \(k\) 次的方案数;\(g[x,k]\) 表示保证处理完 \(y\) 之后,\(x\) 顶上那条边是实边,操作了 \(k\) 次的方案数。

则有状态转移方程:

\[\begin{aligned} f[x,k]&=\sum\limits_{t=0}^{k} tmpf[t]\times f[y,k - t]\\ g[x,k]&=\sum\limits_{t = 0}^k(tmpg[t]\times f[y,k - t] + tmpf[t]\times g[y,k - t]) \end{aligned} \]

在整个更新完 \(x\) 的答案之后,我们将 \(g[x]\) 数组中的值全部赋给 \(f[x]\),此时 \(f[x,k]\) 的意义变成 \(x\) 顶上那条边不是实边,操作了 \(k\) 次的方案数。为什么可以直接将 \(g[x]\) 数组中的值赋给 \(f[x]\) 呢?因为 \(x\) 顶上的边不是实边,只有可能是 \(x\) 的父亲用了一次 access,把原本是实边的边变成了虚边。

最后有一个小细节。就是最后将 \(g\) 赋给 \(f\) 的时候,\(g[x,0]\) 是等于 0 的,而 \(g[x,1]\) 是大于 0 的。赋给 \(f\) 了之后,为了保证意义上的自洽以及树的形态不能重复,要将 \(f[x,0]\gets 1\)\(f[x,1]\gets f[x,1]-1\)

由于 \(k\leq siz_x\),所以这是一个经典的 \(O(nK)\) 的 DP。

三、代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;
#define FILEIN(s) freopen(s, "r", stdin)
#define FILEOUT(s) freopen(s, "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int MAXN = 10005, MOD = 998244353, MAXK = 505;

int n, K, siz[MAXN];
int head[MAXN], tot;
long long f[MAXN][MAXK], g[MAXN][MAXK];
long long tmpf[MAXK], tmpg[MAXK];

struct Edge {
    int y, next;
    Edge() {}
    Edge(int _y, int _next) : y(_y), next(_next) {}
}e[MAXN << 1];

inline void connect(int x, int y) {
    e[++ tot] = Edge(y, head[x]);
    head[x] = tot;
}

void dfs(int x, int fa) {
    siz[x] = 1;
    g[x][1] = 1;
    f[x][0] = 1;
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].y;
        if (y == fa) continue;
        dfs(y, x);

        for (int k = 0; k <= min(K, siz[x]); ++ k) {
            tmpf[k] = f[x][k];
            tmpg[k] = g[x][k];
            f[x][k] = g[x][k] = 0;
        }

        for (int j = 0; j <= min(K, siz[x]); ++ j) {
            for (int k = 0; k <= siz[y] && j + k <= K; ++ k) {
                (f[x][j + k] += tmpf[j] * f[y][k] % MOD) %= MOD;
                (g[x][j + k] += tmpf[j] * g[y][k] % MOD + tmpg[j] * f[y][k] % MOD) %= MOD;
            }
        }
        siz[x] += siz[y];
    }
    memcpy(f[x], g[x], sizeof f[x]);
    f[x][0] = 1; -- f[x][1];
}

int main() {
    FILEIN("access.in"); FILEOUT("access.out");
    n = read(); K = read();
    for (int i = 1; i < n; ++ i) {
        int x = read(), y = read();
        connect(x, y); connect(y, x);
    }
    dfs(1, 0);
    long long res = 0;
    for (int i = 0; i <= min(n, K); ++ i) (res += f[1][i]) %= MOD;
    printf("%lld\n", res);
    return 0;
}
posted @ 2021-07-01 22:59  蓝田日暖玉生烟  阅读(265)  评论(0编辑  收藏  举报