CF1930G Prefix Max Set Counting 题解

CF1930G Prefix Max Set Counting
给定一棵以 1 为根的有根树,求出其所有 dfs 序中前缀最大值序列的数量。\(n\le 10^6\)

思路

显然考虑 DP。
由于是求前缀最大值序列的方案数,因此如果一些点要出现在这个序列中,其到根节点上一定没有比它大的节点。
因此我们设 \(f_i\) 表示以权值为 \(i\) 的点为结尾的序列数量。可以发现权值最大的一定在这个序列末尾,权值最小的根节点 1 一定在这个序列的开头,因此这个 DP 的初始状态是 \(f_1=1\),答案就是 \(f_n\)

我们定义某个点对另一些点“有贡献” 指这另一些点的方案数可以从这个点转移过来。具体而言,这个转移就是使当前点作为序列倒数第二个点,被贡献的点作为第一个点,去加被贡献的点的方案数。
发现如果按树点的权值顺序从小到大枚举,可以通过刷表法来维护某一个点。因为只有小的点可能对大的点有贡献,而大的点不可能对小的点有贡献,因此这样统计是完备的。

按这个思路,我们来分类讨论就可以计算贡献了。假设当前点为 \(i\),其权值同样也为 \(i\)。(注意我们是按权值大小的顺序枚举的)

  1. \(i\) 为根的子树内没有比它大的点。
    那就考虑它什么时候可以作为序列的倒数第二个点。我们向上枚举 \(i\) 的祖先 \(u\),假设 \(u\) 及其子树中权值最大的点是 \(j\),那么当 \(j>i\) 的时候,\(u\) 及其子树中的点就有了一种走的方式:
    先走到 \(i\),再走到 \(j\) 就保证以 \(j\) 自己结尾的方案数必定可以累加上 \(i\) 的方案数。因此我们直接给 \(u\) 子树中权值比 \(i\) 大的方案数加上 \(f_i\) 即可。
    注意这里找 \(u\) 可以直接暴力跳父亲,因为有均摊,找 \(u\) 的总复杂度 \(O(n)\)

  2. \(i\) 为根的子树内有比其大的点。
    因为 dfs 序是一定要一棵子树搜完以后再去其他子树,因此对于除了以 \(i\) 为根的其他子树而言 \(i\) 作为序列的倒数第二位已经不可能了。
    因此直接将 \(i\) 自己子树中权值大于 \(i\) 的节点的方案数加上 \(f_i\) 即可。

然后发现由于权值比 \(i\) 小的所有数全部都已经枚举完了,加不加无所谓,因此加的时候不需要管权值是否大于 \(i\),直接把整棵子树全部都加即可。

于是差分后的树状数组即可满足条件。在 dfs 序上维护,区间加单点查。

code

事实上感觉没有黑题的难度。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+7,p=998244353;
int n,f[N],fa[N],siz[N],dfncnt,dfn[N],mson[N],sign[N],tot[N]; 
vector<int> q[N];
struct node{
	int tr[N];
	void modify(int x,int val){while(x<=n)tr[x]=(tr[x]+val)%p,x+=x&(-x);}
	int query(int x){int res=0;while(x)res=(res+tr[x])%p,x-=x&(-x);return res;}
}g;
void dfs1(int u,int fr,int t){
	siz[u]=1;fa[u]=fr;dfn[u]=++dfncnt;mson[u]=0;sign[u]=t<u?1:0;
	for(int i=0;i<tot[u];i++){
		int v=q[u][i];if(v==fr) continue;
		dfs1(v,u,max(t,u));siz[u]+=siz[v];mson[u]=max({mson[u],v,mson[v]});
	}
}
void solve(){
	for(int i=0;i<=n;i++) q[i].clear(),tot[i]=0,f[i]=0,sign[i]=0,g.tr[i]=0;dfncnt=0;
	cin>>n;for(int i=1,u,v;i<=n-1;i++){cin>>u>>v;q[u].push_back(v),tot[u]++;q[v].push_back(u),tot[v]++;}
	dfs1(1,0,0);f[1]=1;g.modify(1,1);
	for(int i=2;i<=n;i++){
		if(!sign[i]) continue;
		f[i]=g.query(dfn[i]);g.modify(dfn[i]+siz[i],f[i]),g.modify(dfn[i],p-f[i]);
		if(i>mson[i]){
			int u=i;
			while((max(u,mson[u])<=i)&&u) {u=fa[u];}
			if(u==0) continue;
			g.modify(dfn[u],f[i]);g.modify(dfn[u]+siz[u],p-f[i]);
		}
		else g.modify(dfn[i],f[i]),g.modify(dfn[i]+siz[i],p-f[i]);
	}
	cout<<f[n]<<'\n'; 
} 
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int T;cin>>T;while(T--) solve();
	return 0;
}
posted @ 2025-03-26 12:03  all_for_god  阅读(39)  评论(0)    收藏  举报