题意:
给定一个由\(n\)个点组成的树,然后每次可以从树中选择一个顶点删去,定义操作结束后是完美的,但且仅当删点后形成的点集中,每个点都有至少一个点与它相连,问一共有多少种删点方式能够形成完美的局面,答案模\(998244353\)。
思路:
统计方案类的问题,可以往\(dp\)方向靠,又是在树上,那么就是树形\(dp\)了。
题目要求都必须有节点相连才是合法转态且又是否删除这个操作选项,那就考虑状态预设的时候要带上节点连接子节点情况的信息和是否删除的信息,即可用三个状态来囊括。
考虑令
\(dp_{u,0}\)表示当前点删除后能形成的方案数。
\(dp_{u,1}\)表示当前点保留且至少有一个子节点与它相连形成的方案数。
\(dp_{u,2}\)表示当前点保留且没有任何一个子节与它相连的方案数
按照如下方式转移即可。
特别注意第三种转移,会计算到当前点的所有儿子都取删除的这一种情况,不符合题意,应当删去这种情况,至少留有一个子节点,不确定留那个,只能这样用\(size(u)\)个和式相乘转移,乘起来拆开后之后就会是一些包含\(dp[v][0]\)存在\([0,size(u)]\)次的式子,很明我们只需要把存在\(size(u)\)的答案删去即为至少存在一个子节点的答案。
从节点\(1\)开始遍历,可知最终答案即为\(dp[1][0]+dp[1][1]\)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 1e5 + 10;
const ll MOD = 998244353;
int n,siz[N];
std::vector<int>G[N];
ll f[N][3];
//0/1/2记录当前u点的三种状态 0代表删除 1代表保留且该点至少与一个子节点相连 2代表保留且不与任意一个子节点相连
void dfs(int u,int fa) {
f[u][0] = f[u][1] = f[u][2] = 1;
for(auto v : G[u]) {
if(v == fa) continue;
dfs(v,u);
f[u][0] = f[u][0] * (f[v][0] + f[v][1]) % MOD;
f[u][2] = f[u][2] * f[v][0] % MOD;
f[u][1] = f[u][1] * (f[v][0] + f[v][1] + f[v][2]) % MOD;
}
//此时f[u][1]会包含着一种 所有点都不与之相连的状态 所以需要去掉这个状态
f[u][1] = (f[u][1] - f[u][2] + MOD) % MOD;
}
void solve() {
scanf("%d",&n);
for(int i = 1;i <= n;i ++) G[i].clear();
for(int i = 1,u,v;i < n;i ++) {
scanf("%d%d",&u,&v);
G[u].pb(v),G[v].pb(u);
}
dfs(1,-1);
ll ans = (f[1][0] + f[1][1]) % MOD;
printf("%lld\n",ans);
}
int main() {
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}
The 15th Chinese Northeast Collegiate Programming Contest D - Vertex Deletion (树形dp)