首页 > 技术文章 > 2020CCPC秦皇岛H.Holy Sequence

asuldb 2021-10-30 19:30 原文

题目

好久没写题解了,做了个计数感觉有点妙妙,就来口胡一波。

由于我们要对于每一个\(t\)单独计算答案,所以必然要先枚举\(t\),考虑一个\(n\)-holy序列的前缀最大值每次最多只能增加\(1\),必然可以化为\(1......2......3......4......5.......t\)的形式。我们枚举\(t\)第一次出现的位置为\(i\)\(i\)前面的方案数可以用一个dp预处理出来。观察这个dp的转移发现其实就是第二类斯特林数

这样\(t\)出现的位置必然在\(i\)以及\(i\)之后,考虑一个经典的恒等式\(x^2=2\times \binom{x}{2}+\binom{x}{1}\)。以计算\(\binom{x}{2}\)为例,我们完全可以钦定两个位置填\(t\),其余位置构成一个合法序列即可算贡献即可,而注意到我们钦定了\(i\)\(t\),那么\(i\)之后的位置填\(t\)都是合法的,所以我们其实并不是很关心这两个位置到底在哪里,在\(i\)后面用组合数随便钦定两个位置即可。

最后还需要求出\(i\)\(t\)后面的合法方案数,发现从\(i\)填到\(n\)需要强行从\(t\)开始难以处理,于是考虑从\(n\)倒着向\(i\)搞一个dp,这样数是从大到小的,并不需要强行令\(t\)作为开头。

预处理第二类斯特林数和这个反向的\(dp\),就能\(O(n)\)回答一个\(t\)的答案了。

代码

#include<bits/stdc++.h>
#define re signed
const int maxn=3005;
int n,mod,g[maxn][maxn],h[maxn][maxn];
inline int qm(int x) {return x>=mod?x-mod:x;}
inline int calc(int x) {return (1ll*x*(x-1)/2ll)%mod;}
int main() {
	int Test;scanf("%d",&Test);
	for(re int te=1;te<=Test;++te) {
		scanf("%d%d",&n,&mod);
		for(re int i=0;i<=n+1;++i)
			for(re int j=0;j<=n+1;++j)g[i][j]=h[i][j]=0;
		g[0][0]=1;
		for(re int i=0;i<n;i++)
			for(re int j=0;j<=i;++j) {
				if(!g[i][j]) continue;
				g[i+1][j+1]=qm(g[i+1][j+1]+g[i][j]);
				g[i+1][j]=qm(g[i+1][j]+1ll*g[i][j]*j%mod);
			}
		for(re int i=1;i<=n;i++)h[0][i]=1;
		for(re int i=0;i<n;i++)
			for(re int j=n;j;--j) {
				if(!h[i][j]) continue;
				h[i+1][j]=qm(h[i+1][j]+1ll*h[i][j]*j%mod);
				if(j-1>0) h[i+1][j-1]=qm(h[i+1][j-1]+h[i][j]);
			}
		printf("Case #%d:\n",te);
		for(re int t=1;t<=n;++t) {
			int ans=0;
			for(re int i=1;i<=n;i++) {
				if(!g[i-1][t-1]) continue;
				int tot=h[n-i][t];
				if(n-i-1>=0) tot=qm(tot+3ll*h[n-i-1][t]%mod*(n-i)%mod);
				if(n-i-2>=0) tot=qm(tot+2ll*h[n-i-2][t]%mod*calc(n-i)%mod);
				ans=qm(ans+1ll*tot*g[i-1][t-1]%mod);
			}
			printf("%d ",ans);
		}
		puts("");
	}
	return 0;
}

推荐阅读