题目
给定一棵树,给 n − 1 n-1 n−1 条边定向,求恰有 k k k 个点无出边的方案数。
设计 f ( u , i , 0 / 1 ) f(u,i,0/1) f(u,i,0/1) 表示以 u u u 为根的子树内,有 i i i 个无出边, u u u 是否有出边的方案数。
树背包。
考虑转移。对 v ∈ s o n u v \in son_u v∈sonu,枚举 j j j,钦定 v v v 子树贡献为 j j j,则 v v v 之前贡献为 i − j i-j i−j,讨论边的方向:
u → v u \to v u→v,
f ( u , i , 1 ) = ∑ j ( f ( u , i − j + 1 , 0 ) + f ( u , i − j , 1 ) ) × ( f ( v , j , 0 ) + f ( v , j , 1 ) ) f(u,i,1) = \sum_{j} (f(u,i-j+1,0) +f(u,i-j,1)) \times (f(v,j,0)+f(v,j,1)) f(u,i,1)=j∑(f(u,i−j+1,0)+f(u,i−j,1))×(f(v,j,0)+f(v,j,1))
v → u v \to u v→u,
f ( u , i , 1 ) = ∑ j f ( u , i − j , 1 ) × ( f ( v , j + 1 , 0 ) + f ( v , j , 1 ) ) f ( u , i , 0 ) = ∑ j f ( u , i − j , 0 ) × ( f ( v , j + 1 , 0 ) + f ( v , j , 1 ) ) f(u,i,1) = \sum_j f(u,i-j,1) \times (f(v,j+1,0)+f(v,j,1))\\ f(u,i,0) = \sum_j f(u,i-j,0) \times (f(v,j+1,0)+f(v,j,1)) f(u,i,1)=j∑f(u,i−j,1)×(f(v,j+1,0)+f(v,j,1))f(u,i,0)=j∑f(u,i−j,0)×(f(v,j+1,0)+f(v,j,1))
注意树背包滚动数组的特点,倒序枚举,每轮的 f f f 值不要继承。
#include
#define int long long
using namespace std;
const int N = 1005;
const int mod = 998244353;
vector <int> G[N];
int n, t, siz[N], f[N][N][2];
void dfs(int u, int fa)
{
f[u][1][0] = 1, siz[u] = 1;
for(auto v : G[u])
{
if(v == fa) continue;
dfs(v, u), siz[u] += siz[v];
for(int i=min(t, siz[u]); i>=1; i--)
{
int cur0 = 0, cur1 = 0;
for(int j=0; j<=min(i, siz[v]); j++)
{
cur1 += (f[u][i-j][1] + f[u][i-j+1][0]) * (f[v][j][0] + f[v][j][1]) % mod, cur1 %= mod;
cur1 += f[u][i-j][1] * (f[v][j+1][0] + f[v][j][1]) % mod, cur1 %= mod;
cur0 += f[u][i-j][0] * (f[v][j+1][0] + f[v][j][1]) % mod, cur0 %= mod;
}
f[u][i][0] = cur0, f[u][i][1] = cur1;
}
}
}
signed main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i=1; i<n; i++)
{
int u, v;
cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
cin >> t;
dfs(1, 0);
cout << (f[1][t][0] + f[1][t][1]) % mod;
}