AT_abc287_f [ABC287F] Components 题解

题意

给你一棵有 N N N 个节点的树,顶点标号为 1 , 2 , … , N 1,2,\dots,N 1,2,,N ,第 i i i 条边连接的端点是 a i , b i a_i,b_i ai,bi.

对每一个 x = 1 , 2 , … , N x=1,2,\dots,N x=1,2,,N ,解决如下问题:

2 N − 1 2^N-1 2N1 个非空的顶点子集 V V V ,求 V V V 的刚好包含 x x x 个连通块的诱导子图的数量。

什么是诱导子图?

S S S 是图 G G G 的顶点的子集, S S S 的诱导子图是包含 S S S 的所有顶点和这些顶点之间的所有边,这些边的两个端点都必须在 S S S 中。

思路

不难看出这题是一道树形动态规划,我们可以用 d p ( i , j , 0 ) dp(i,j,0) dp(i,j,0) 表示在 i i i 的子树上有 j j j 块并且不选第 i i i 块的方案数,用 d p ( i , j , 1 ) dp(i,j,1) dp(i,j,1) 表示在 i i i 的子树上有 j j j 块并且选第 i i i 块的方案数。

由题意得,我们假如不选,则子节点可选可不选。如果选,则子节点要是选则选的块数是一样的,要是不选就要多加一块。

所以,我们可以分三类来讨论:

i i i 点选择,可得:
d p ( i , j + k , 0 ) = ∑ x ∈ i s o n d p ( i , j , 0 ) × ( d p ( x , k , 1 ) + d p ( x , k , 0 ) ) dp(i,j+k,0)= \sum_{x\in ison} dp(i,j,0)\times (dp(x,k,1)+dp(x,k,0)) dp(i,j+k,0)=xisondp(i,j,0)×(dp(x,k,1)+dp(x,k,0))
i i i 点不选则且 i i i 点的子节点也不选,可得:
d p ( i , j + k − 1 , 1 ) = ∑ x ∈ i s o n d p ( i , j , 1 ) × d p ( x , k , 1 ) dp(i,j+k-1,1)= \sum_{x\in ison} dp(i,j,1)\times dp(x,k,1) dp(i,j+k1,1)=xisondp(i,j,1)×dp(x,k,1)
i i i 点不选则且 i i i 点的子节点选,可得:
d p ( i , j + k , 1 ) = ∑ x ∈ i s o n d p ( i , j , 1 ) × d p ( x , k , 0 ) dp(i,j+k,1)= \sum_{x\in ison} dp(i,j,1)\times dp(x,k,0) dp(i,j+k,1)=xisondp(i,j,1)×dp(x,k,0)

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
struct op {
    int from, to, next;//链式前向星
} a[10005];
int head[5005], cnt;
void add(int x, int y) { a[cnt].from = x, a[cnt].to = y, a[cnt].next = head[x], head[x] = cnt++; }//链式前向星存图 
const int mod = 998244353;
int son_sum[5005];
int dp[5005][5005][2];
bool vis[5005];
void dfs(int x, int father) {
    dp[x][0][0] = dp[x][1][1] = 1;//初始化 
    son_sum[x] = 1;//初始化 
    for (int i = head[x]; i != -1; i = a[i].next) {
        int y = a[i].to;
        if (y == father)
            continue;
        dfs(y, x);
        for (int j = son_sum[x]; j >= 0; j--) {
            for (int k = son_sum[y]; k >= 1; k--) {
            	//根据dp式 
                dp[x][j + k][0] =
                    ((dp[x][j + k][0]) + ((dp[x][j][0] * ((dp[y][k][1] + dp[y][k][0]) % mod)) % mod)) % mod;
                if (j == 0)
                    continue;
                dp[x][j + k][1] = ((dp[x][j + k][1]) + ((dp[x][j][1] * dp[y][k][0]) % mod)) % mod;
                dp[x][j + k - 1][1] = ((dp[x][j + k - 1][1]) + ((dp[x][j][1] * dp[y][k][1]) % mod)) % mod;
            }
        }
        son_sum[x] += son_sum[y];//记录当前子树一共有多少个点 
    }
}
signed main() {
    freopen("block.in", "r", stdin);
    freopen("block.out", "w", stdout);
    memset(head, -1, sizeof(head));
    scanf("%lld", &n);
    for (int i = 1, x, y; i < n; i++) {
        scanf("%lld%lld", &x, &y);
        add(x, y), add(y, x);//建边 
    }
    dfs(1, -1);
    for (int i = 1; i <= n; i++) {
        printf("%lld\n", (dp[1][i][0] + dp[1][i][1]) % mod);//输出答案,记得取模 
    }
    return 0;
}
  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值