虽然题目不难,但是这应该是我第一次在考场上成功拿到计数题的不算低的分数,值得记录
如果对序列处理出$i$后面第一个比它大的位置$r_i$,那么两个序列同构的条件就是$r_i$都相同,而$r_i$构成一棵树,考虑计数树的数量
更进一步,我们只需计数那些由$1\cdots n$的排列生成的深度$\leq m$的树,因为用$[1,m]$中的数不能生成深度$\gt m$的树,生成这样的树的排列也可以通过恰当安排变成数字范围为$[1,m]$的序列
于是可以DP,设$f_{i,j}$表示深度$\leq i$,节点数为$j$的树的数量,枚举$j$在排列中的位置,有$f_{i,0}=1,f_{i,j}=\sum\limits_{k=0}^{j-1}f_{i-1,k}f_{i,j-1-k}$
设$f_{i,0\cdots n}$的生成函数为$F_i$,有$F_0=1,F_i=\frac1{1-xF_{i-1}}$
考场上就做到这里,$O(n^2\log n)$可以拿$70$分
$F_i$可以表示为$\frac{a_i}{b_i}$的形式,其中$a_i,b_i$都是$i$次多项式,推一下就可以矩阵快速幂
直接做显然不行,但因为是线性变换所以先DFT,对点值矩阵快速幂后IDFT回去即可
总时间复杂度$O(n\log n)$
#include#include #include using namespace std;typedef long long ll;const int mod=998244353;int mul(int a,int b){return(ll)a*b%mod;}int ad(int a,int b){return(a+=b)>=mod?a-mod:a;}int de(int a,int b){return(a-=b)<0?a+mod:a;}int pow(int a,int b){ int s=1; while(b){ if(b&1)s=mul(s,a); a=mul(a,a); b>>=1; } return s;}int rev[262144],N,iN;void pre(int n){ int i,k=0; for(N=1,k=0;N <<=1)k++; for(i=0;i >1]>>1)|((i&1)<<(k-1)); iN=pow(N,mod-2);}void ntt(int*a,int on){ int i,j,k,t,w,wn; for(i=0;i >1;k++){ t=mul(a[i/2+j+k],w); a[i/2+j+k]=de(a[j+k],t); a[j+k]=ad(a[j+k],t); w=mul(w,wn); } } } if(on==-1){ for(i=0;i >1); pre(n<<1); memset(t1,0,N<<2); memcpy(t1,a,n<<2); ntt(t1,1); ntt(b,1); for(int i=0;i >=1; } return s;}int a[262144],b[262144],c[262144];int main(){ int n,m,i,k,wn,w; scanf("%d%d",&n,&m); if(m>n){ printf("0"); return 0; } for(k=1;k<=n;k<<=1); pre(k*2); wn=pow(3,(mod-1)/N); w=1; for(i=0;i