*题解:CF2229E Deconstruction Tree

题目链接

解析

数数题,考虑 dp。

考虑树形 dp,发现状态设不出来,因为选叶子的过程是在各个子树之间横跳的,设一个只包含一个子树内信息的状态显然无法解决问题。

然后发现状态更设不出来了,返回去找性质。于是发现一个结论:在操作过程中,在加入当前选中叶子 \(x\)\(S\) 后,\(x = S_{\max}\),也就是说 \(x\) 是单调不减的。这也意味着 \(x\) 想要被选,必须要求 \(S\) 中的最大值 \(S_{\max} \le x\)

要想选中 \(x\),还需要 \(x\) 变为叶子,即以 \(x\) 为根,将其儿子删到只留下一个,根据上一条结论,删掉的结点中,其最大编号应当小于 \(x\)。由于有结点 \(n\) 的存在,如果 \(n\) 变为叶子就会把其它结点全部删完。所以如果 \(x\neq n\) 能被选中,那么留下来的那个儿子的子树必定是 \(n\) 所在的子树。直接令 \(n\) 为根,就只用看所有儿子子树内编号最大值了。

\(f_i\) 表示操作过程中以 \(i\) 为最大值的不同 \(S\) 个数,\(g_i\) 表示 \(i\) 的儿子的子树内结点编号最大值。在 \(i\) 加入前,\(S_{\max} < i\),同时由于有 \(g_i\) 的存在,为了删掉 \(g_i\) 及编号更小的点,需要有 \(S_{\max} > g_i\)。据此,可列出状态转移方程:

\[f_i=\sum_{j=g_i + 1} ^ {i - 1} f_j \]

注意 \(n\) 是例外的,因为 \(n\) 为根,其能被选时,即度为 \(1\) 时可以连接着一棵子树,这棵子树必定为 \(n - 1\) 所在子树。故改变 \(g_n\) 定义,求 \(g_n\) 时避开 \(n - 1\) 所在子树。

\(f\) dp 的时候求前缀和,转移时差分即可做到 \(O(n)\) 的时间复杂度。

代码

转移时注意判 \(g_i < i\)

/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 2e5 + 5,M = 10 + 5,L = 19,L2 = 17,mod = 998244353;
vector<int> t[N];
int f[N],g[N],pre[N];
int mx;
void dfs(int x,int f){
	for(int nx : t[x])if(nx != f){
		dfs(nx,x);
		g[x] = max({g[x],g[nx],nx});	
	}
	if(t[x].size() == 1){
		mx = max(mx,x);
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;
	cin>>T;
	while(T--){
		int n;
		cin>>n;
		for(int i=1;i<n;i++){
			int u,v;
			cin>>u>>v;
			t[u].push_back(v);
			t[v].push_back(u);
		}
		dfs(n,0);
		g[n] = 0;
		for(int nx : t[n]){
			if(max(g[nx],nx) != n - 1){
				g[n] = max(g[n],max(g[nx],nx));
			}
		}
		f[mx] = 1;
		pre[mx] = 1;
		for(int i=mx + 1;i<=n;i++){
			if(g[i] < i)
				f[i] = (pre[i - 1] + mod - pre[g[i]]) % mod;
			pre[i] = (pre[i - 1] + f[i]) % mod;
		}
		cout<<f[n]<<'\n';
		for(int i=0;i<=n;i++){
			t[i].clear(); 
			f[i] = pre[i] = g[i] = 0;
		}
		mx = 0;
	}
	return 0;
}

posted @ 2026-05-26 18:36  yutar  阅读(37)  评论(0)    收藏  举报