专栏文章

题解:AT_abc287_f [ABC287F] Components

AT_abc287_f题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mio0390f
此快照首次捕获于
2025/12/02 11:10
3 个月前
此快照最后确认于
2025/12/02 11:10
3 个月前
查看原文
一个显然的树上背包。
dpu,k,0/1dp_{u,k,0/1} 表示以 uu 为根的子树中有 kk 个连通块,且 uu 是否在点集中的方案数。
枚举 uu 当前子树的连通块个数 iiuu 的子节点 vv 的连通块个数 jj,转移方程:
  1. uu 不选,vv 随意:dpu,i+j,0=dpu,i,0×(dpv,j,0+dpv,j,1)dp_{u,i+j,0}=dp_{u,i,0} \times (dp_{v,j,0} + dp_{v,j,1})
  2. uu 选,vv 不选:dpu,i+j,1=dpu,i,1×dpv,j,0dp_{u,i+j,1} = dp_{u,i,1} \times dp_{v,j,0}
  3. u,vu,v 都选,此时 uu 所在的连通块和 vv 所在的连通块组成了一个大连通块:dpu,i+j1,1=dpu,i,1×dpv,j,1dp_{u,i+j-1,1} = dp_{u,i,1} \times dp_{v,j,1}
主要是代码的实现:
  1. 只有 uu 的子树中至少有一个连通块时才能选 uu 本身。
  2. 为了避免重算,需要将 dpudp_{u} 复制到 tt 中,将 dpudp_u 清空,再使用 tt 转移 dpudp_u
  3. 记得取模。

代码

CPP
#include <bits/stdc++.h>

using namespace std;

#define ll long long

const int N = 5050, MOD = 998244353;

int n, dp[N][N][2], t[N][2], sz[N];
vector<int> e[N];

void dfs(int now, int fa){
	sz[now] = 1; dp[now][1][1] = dp[now][0][0] = 1;
	for(int u : e[now]){
		if(u == fa) continue;
		dfs(u, now);
		for(int i = 0; i <= sz[now]; i ++){
			t[i][0] = dp[now][i][0];
			t[i][1] = dp[now][i][1];
			dp[now][i][0] = dp[now][i][1] = 0;
		}
		for(int i = 0; i <= sz[now]; i ++){
			for(int j = 0; j <= sz[u]; j ++){
				dp[now][i + j][0] = (dp[now][i + j][0] + (ll)t[i][0] * dp[u][j][0] % MOD) % MOD;
				if(j > 0) dp[now][i + j][0] = (dp[now][i + j][0] + (ll)t[i][0] * dp[u][j][1] % MOD) % MOD;
				if(i > 0) dp[now][i + j][1] = (dp[now][i + j][1] + (ll)t[i][1] * dp[u][j][0] % MOD) % MOD;
				if(i > 0 && j > 0) dp[now][i + j - 1][1] = (dp[now][i + j - 1][1] + (ll)t[i][1] * dp[u][j][1] % MOD) % MOD;
			}
		}
		sz[now] += sz[u];
	}
}

int main() {
	cin >> n;
	for(int i = 1, u, v; i < n; i ++){
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	} 
	dfs(1, 0);
	for(int j = 1; j <= n; j ++)
		cout << (dp[1][j][0] + dp[1][j][1]) % MOD << endl;
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...