count

对于一个长度为 \(n\) 的整数序列 \(A\),若其满足下列条件,则称其为极好序列:

  • 对于任意 \(i \in [1,n]\)\(a_i \in [1,m]\)

  • 对于任意 \(x \in [1,m]\),存在 \(i \in [1,n]\) 使得 \(a_i = x\)

\(f(A:[l,r])\) 为子区间 \([l,r]\) 最大值的下标,若有多个最大值取最小的下标。

定义极好序列 \(A_1\)\(A_2\) 本质相同,当且仅当其满足如下条件:

  • 对于任意 \([l,r]\),满足 \(f(A_1:[l,r]) = f(A_2:[l,r])\)

求本质不同的极好序列个数,对 \(998244353\) 取模。


\(n < m\) 时答案为 \(0\),下面默认 \(n \geq m\)

对于最大值,我们考虑使用笛卡尔树来刻画。

显然,\(A_1\)\(A_2\) 所有区间的最大值下标相同,等价于两个序列的笛卡尔树相同。

也就是说,我们只需要对笛卡尔树计数即可。

笛卡尔树对于所有节点 \(u\),需要有 \(a_u > a_{lc_u}\)\(a_u \geq a_{rc_u}\)


首先我们尝试找到一个笛卡尔树合法的必要条件。

\(a_u > a_{lc_u}\) 出发,这是唯一能使值减小的方法。

\(m\)\(1\),值最多减少 \(m-1\) 次,不妨记 \(u\)\(lc_u\) 的边为有效边。

也就是说,任意一个点到根的路径上,最多只有 \(m-1\) 条有效边。

容易发现这也是一个充分条件,构造是容易的。


接下来对笛卡尔树计数,我们发现笛卡尔树比较难以刻画。

联想到左儿子右兄弟的表示方法,考虑反着应用。

笛卡尔树上的有效边,在当前表示方法中,意味着新增一个儿子节点。

而一条链上的有效边不超过 \(m-1\) 条,意味着新的树的深度不超过 \(m\)

也就是说,笛卡尔树可以看作一棵深度不超过 \(m\) 的多叉树。

由于笛卡尔树的根节点存在右儿子,所以多叉树需要补充一个根节点。


定义根节点深度为 \(0\),我们需要对 \(n+1\) 个点,每个点的深度 \(\leq m\) 的树计数。

考虑用括号序列描述树,显然多叉树可以和合法括号序列构成双射。

考虑先去除根节点,这样原题就可以看作对长度为 \(2n\) 的括号序列计数,使得:

  • 对于每个前缀 \([1,i]\),左括号与右括号的个数差 \(\in [0,m]\)

我们不妨将其看成格路计数,则我们需要做的就是从 \((0,0)\) 走到 \((n,n)\)

每一步可以向上或向右走,要求不经过 \(y = x - 1\)\(y = x + m + 1\)

我们考虑进行容斥。所有走法的总数应该是 \(\dbinom{2n}{n}\),接下来我们需要减去不合法的方案。

我们不妨记第一条线为 \(A\),第二条线为 \(B\),将不合法的方案经过的线记录下来。

我们发现其必然是一个 \(A\)\(B\) 构成的字符串,不妨将连续段合并成一个字符。

使用经典的翻折路径方法即可解决问题,复杂度 \(O(n)\)

#include<iostream>
#include<cstdio>
using namespace std;
const long long mod=998244353;
const int N=200000;
long long fact[N+10],invfact[N+10];
long long inv(long long num){
	long long pre=mod-2,ans=1;
	while(pre){
		if(pre&1){
			ans=ans*num%mod;
		}
		num=num*num%mod;
		pre>>=1;
	}
	return ans;
}
void init(){
	fact[0]=invfact[0]=1;
	for(int i=1;i<=N;i++){
		fact[i]=fact[i-1]*i%mod;
		invfact[i]=invfact[i-1]*fact[i]%mod;
	}
	long long tmp=inv(invfact[N]);
	for(int i=N;i>=1;i--){
		invfact[i]=invfact[i-1]*tmp%mod;
		tmp=tmp*fact[i]%mod;
	}
}
int n,m;
long long C(int num){
	return fact[2*n]*invfact[num]%mod*invfact[2*n-num]%mod;
}
long long solve1(){
	long long ans=0;
	int pre=0;
	bool flag=false;
	while(true){
		if(flag==false){
			pre=-1-pre;
		}
		else{
			pre=m+1-pre;
		}
		if(pre<-n  ||  pre>n){
			break;
		}
		if(flag==false){
			ans+=C(pre+n);
		}
		else{
			ans-=C(pre+n);
		}
		ans%=mod;
		flag=!flag;
	} 
	return ans;
}
long long solve2(){
	long long ans=0;
	int pre=0;
	bool flag=false;
	while(true){
		if(flag==true){
			pre=-1-pre;
		}
		else{
			pre=m+1-pre;
		}
		if(pre<-n  ||  pre>n){
			break;
		}
		if(flag==false){
			ans+=C(pre+n);
		}
		else{
			ans-=C(pre+n);
		}
		ans%=mod;
		flag=!flag;
	} 
	return ans;
}
int main(){
	init();
	scanf("%d %d",&n,&m);
	if(m>n){
		printf("0");
		return 0;
	}
	long long ans=C(n);
	ans-=solve1();
	ans=(ans%mod+mod)%mod;
	ans-=solve2();
	ans=(ans%mod+mod)%mod;
	printf("%lld",ans);
	return 0;
}
posted @ 2026-01-11 20:22  Oken喵~  阅读(8)  评论(1)    收藏  举报