CF1930G Prefix Max Set Counting 题解

太巧妙了!自己做的时候完全没有思路,连题解都看了好久。

前置知识

树,dp

思路

最开始完全不会的原因是想要正向的做,统计所有的方案的贡献,这个确实完全做不了,又是 \(dfs\) 序又是前缀最大值不好统计。所以我们从答案入手,我们发现求本质不同的前缀最大值序列,我们可以设 \(f_i\) 表示以 \(i\) 为结尾的序列的方案数我们只需要考虑能否存在一种可能即可。
我们考虑转移肯定是 \(f_i=\sum_{j<i}f_j\) 但是 \(j\) 不能任意选。
我们发现 \(j\) 满足一些条件:

  1. $j<i $
  2. \(i\) , \(j\) 都是到根的最大值
  3. \(i\) 是当前保留的子树最大值

前面两个性质显然,什么是当前保留的子树,对于一个点我们遍历子树是将子树的最大值保留,遍历完将子树最大去掉,保留这个点的子树最大值。显然只有它能对后面做贡献。
只要满足了这些性质我们就可构造出方法来是它的前缀最大值序列符合定义。但是我们发现如果直接遍历每个点可能导致漏或重。所以我们对子树按子树最大值排序从小到大遍历即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int mod=998244353,N=1e6+10;
#define int long long
vector<int> ve[N];
int n,a[N],mx[N],up[N],sh[N],f[N];
int lowbit(int x){return x&(-x);}
void add(int x,int z){while(x<=n){sh[x]=(sh[x]+z)%mod;x+=lowbit(x);}}
int ask(int x){int res=0;while(x){res=(res+sh[x])%mod;x-=lowbit(x);}return res;}
void dfs1(int u,int fa)
{
	mx[u]=u;
	int len=ve[u].size();
	for(int i=0;i<len;i++)
	{
		int v=ve[u][i];
		if(v==fa)continue;
		dfs1(v,u);
		mx[u]=max(mx[u],mx[v]);
	}
}
bool cmp(int x,int y)
{
	return mx[x]<mx[y];
 } 
void dfs2(int u,int fa)
{
	up[u]=max(up[fa],fa);
	if(u>up[u])
	{
		if(u==1)f[u]=1;//初始化
		else f[u]=(f[up[u]]+(ask(u)-ask(up[u]-1)+mod)%mod)%mod; //从链上的位置转,或则从别的子树的最大转 
	}
	sort(ve[u].begin(),ve[u].end(),cmp);//按mx排序 
	int len=ve[u].size();
	for(int i=0;i<len;i++)
	{
		int v=ve[u][i]; 
		if(v==fa)continue;
		dfs2(v,u);
		add(mx[v],f[mx[v]]);
	} 
	for(int i=0;i<len;i++)
	{
		int v=ve[u][i];
		if(v==fa)continue;
		add(mx[v],-f[mx[v]]);
	}
}
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)ve[i].clear(),f[i]=sh[i]=0;//清空
	for(int i=1;i<n;i++)
	{
		int u,v;cin>>u>>v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,0);
	cout<<f[n]<<'\n';
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int t;cin>>t;
	while(t--)solve();
	return 0;
}
posted @ 2025-03-25 10:28  exCat  阅读(30)  评论(1)    收藏  举报