UOJ424 count
count
如果一个序列满足序列长度为\(n\),序列中的每个数都是\(1\)到\(m\)内的整数,且所有\(1\)到\(m\)内的整数都在序列中出现过,则称这是一个挺好序列。
对于一个序列\(A\),记\(f_A(l, r)\)为\(A\)的第\(l\)个到第\(r\)个数中最大值的下标(如果有多个最大值,取下标最小的)。
两个序列\(A\)和\(B\)同构,当且仅当\(A\)和\(B\)长度相等,且对于任意\(i\le j\),均有\(f_A(i,j)=f_B(i,j)\)。
给出\(n,m\),求有多少种不同构的挺好序列。答案对\(998244353\)取模。
\(n,m\leq 100000\)。
题意转化
根据笛卡尔树的性质直接转化,得到正常题意
计数有多少大小为\(N\)的二叉树,满足从根节点走到任意一个节点向左走的次数不超过\(M~(M=m-1)\)。
\(1 ≤ N ≤ 10^6\)。
题解
首先有一个\(O(NM)\)的暴力DP。考虑按照先序遍历的顺序遍历这颗二叉树;那么在每个点处要么向左走,要么跳到其某个祖先(包括自己)处然后往右走。
那么DP的时候只需要记录当前是先序遍历的第几个点,以及向左走了多少次。转移用前缀和可以优化到线性。
具体而言,用 \(f(i,j)\) 表示先序遍历第 \(i\) 个点,它的祖先中有 \(j\) 个是往左走的方案数。转移:
-
\(f(i,j)\leftarrow f(i+1,j+1)\) 走到左儿子的方案数。
-
\(f(i,j)\leftarrow f(i+1,k \leq j)\) 走到某个祖先的右儿子的方案数。这个显然可以前缀和优化。
这样做实际上在统计合法的先序遍历的序列的方案数,而每个先序遍历的序列一一对应了一个二叉树。
CO int M=1e5+10;
int fac[2*M],ifac[M];
CO int N=2e3+10;
int f[N][N],g[N][N];
int main(){
int n=read<int>(),m=read<int>();
if(m>n){
puts("0");
return 0;
}
if(m>=n-1){
fac[0]=1;
for(int i=1;i<=2*n;++i) fac[i]=mul(fac[i-1],i);
ifac[n+1]=fpow(fac[n+1],mod-2);
for(int i=n;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
int ans=mul(fac[2*n],mul(ifac[n],ifac[n+1]));
printf("%d\n",ans-(m==n-1));
return 0;
}
--m;
fill(f[n],f[n]+m+1,1);
copy(f[n],f[n]+m+1,g[n]);
for(int j=1;j<=m;++j) g[n][j]=add(g[n][j-1],g[n][j]);
for(int i=n-1;i>=1;--i){
for(int j=0;j<=m;++j) f[i][j]=add(f[i+1][j+1],g[i+1][j]);
copy(f[i],f[i]+m+1,g[i]);
for(int j=1;j<=m;++j) g[i][j]=add(g[i][j-1],g[i][j]);
}
printf("%d\n",f[1][0]);
return 0;
}
优化DP可以考虑组合意义,即计数有多少个长度为\(N\)的非负整数序列\(A\),满足:
- \(0 ≤ A_i ≤ M\)
- \(A_1 = 0\)
- \(∀2 ≤ i ≤ N, A_i ≤ A_{i−1} + 1\)
特判掉\(M = 0\)的特殊情况,对于第三条限制,常见的变换手段是令\(P_i = i − A_i\)。
-
\(i − M ≤ P_i ≤ i\)
-
\(P_1 = 1,~P_{N+1} = N + 1\)
-
\(P_i ≥ P_{i−1}\)
即从\((1,1)\)走到\((N + 1,N + 1)\),每次只能往右走和往上走,把每个\(x\)处的最高点当作\(P_x\)的值,那么不能经过\(y = x − (M + 2)\)和\(y =x + 1\)两条直线的方案数。因为把最高点当做 \(P_x\) 的值,所以可以经过 \(y=x-(M+1)\) 这条直线。
多次翻折法,时间复杂度 \(O(N)\)。
CO int N=2e5+10;
int fac[N],ifac[N];
IN int C(int n,int m){
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int main(){
int n=read<int>(),m=read<int>();
if(m>n){
puts("0");
return 0;
}
--m;
fac[0]=1;
for(int i=1;i<=2*n;++i) fac[i]=mul(fac[i-1],i);
ifac[2*n]=fpow(fac[2*n],mod-2);
for(int i=2*n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
int ans=0,X=n+1,Y=n+1;
for(int i=1;;++i){
swap(X,Y);
if(i&1) X-=1,Y+=1;
else X+=m+2,Y-=m+2;
if(X<1 or Y<1) break;
int sum=C(X-1+Y-1,X-1);
ans=add(ans,i&1?sum:mod-sum);
}
X=n+1,Y=n+1;
for(int i=1;;++i){
swap(X,Y);
if(i&1) X+=m+2,Y-=m+2;
else X-=1,Y+=1;
if(X<1 or Y<1) break;
int sum=C(X-1+Y-1,X-1);
ans=add(ans,i&1?sum:mod-sum);
}
X=n+1,Y=n+1;
ans=add(C(X-1+Y-1,X-1),mod-ans);
printf("%d\n",ans);
return 0;
}