#树形dp#B 预算缩减

题目

给定一棵树,你需要删去一些边(可以不删),使得剩下的图中每个点所在的连通块大小都\(\geq k\)
求删边的方案数,对\(786433\)取模。两种方案不同,当且仅当存在一条边在一个方案中被删去,而在另外一个方案中没有被删去。


分析

\(dp[x][k]\)表示以点\(x\)为根的子树中\(x\)所在的连通块大小为\(k\)的方案数
状态转移方程显然,每次都要计算断了一条边的贡献,当且仅当子节点所在的连通块大小不小于\(k\)
但是这样\(O(n^3)\)会T掉,考虑当中有很多冗余状态,那么记录子树大小,按照子树大小拼接,时间复杂度\(O(n^2)\)


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N = 5011, mod = 786433;
struct node {
    int y, next;
} e[N << 1];
int dp[N][N], son[N], f[N], ans, as[N], n, k = 1, m;
inline signed iut() {
    rr int ans = 0;
    rr char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
    return ans;
}
inline signed mo(int x, int y) { return x + y >= mod ? x + y - mod : x + y; }
inline void dfs(int x, int fa) {
    dp[x][1] = son[x] = 1;
    for (rr int i = as[x], s; i; i = e[i].next)
        if (e[i].y != fa) {
            dfs(e[i].y, x), son[x] += son[e[i].y], s = 0;
            for (rr int j = 1; j <= son[x]; ++j) f[j] = 0;
            for (rr int j = 1; j <= son[x] - son[e[i].y]; ++j)
                for (rr int o = 1; o <= son[e[i].y]; ++o)
                    f[j + o] = mo(f[j + o], 1ll * dp[x][j] * dp[e[i].y][o] % mod);
            for (rr int o = m; o <= son[e[i].y]; ++o) s += dp[e[i].y][o];
            for (rr int j = 1; j <= son[x]; ++j) dp[x][j] = mo(1ll * dp[x][j] * s % mod, f[j]);
        }
}
signed main() {
    freopen("cut.in", "r", stdin);
    freopen("cut.out", "w", stdout);
    n = iut();
    m = iut();
    for (rr int i = 1; i < n; ++i) {
        rr int x = iut(), y = iut();
        e[++k] = (node){ y, as[x] }, as[x] = k;
        e[++k] = (node){ x, as[y] }, as[y] = k;
    }
    dfs(1, 0);
    for (rr int i = m; i <= son[1]; ++i) ans = mo(ans, dp[1][i]);
    return !printf("%d", ans);
}
posted @ 2020-10-13 16:24  lemondinosaur  阅读(168)  评论(0)    收藏  举报