首页 > 技术文章 > LGP4609题解

lmpp 2022-01-11 14:53 原文

题意简单明确(

很容易知道最高的位置一定是左边能看到最高的和右边能看到最高的。于是我们考虑一个 dp:

\(dp[n][A][B]\) 表示长度为 \(n\) 的排列,左边有 \(A\) 个 balabala,右边有 \(B\) 个 balabala。

我们考虑每次令整个排列的元素全部加一,然后插入一个 \(1\)

很明显,如果 \(1\) 插在最左边,会令 \(A+1\),如果插在最右边会令 \(B+1\),否则都不变。

于是得到了递推方程:

\[dp[n][A][B]=dp[n-1][A-1][B]+dp[n-1][A][B-1]+(n-2) \times dp[n-1][A][B] \]

这样会得到 20pts 的好成绩。吸口氧不知道能不能跑快点儿。

观察 dp 的递推式,发现其存在组合意义:从 \((1,1,1)\)\((n,A,B)\),令 \(n+1\) 会使 \(prod\) 乘上一个 \(n-1\) 。问所有路径的权值之和。

所以相当于从 \((1,n]\) 中选 \(A+B-2\) 个位置,其中 \(A-1\) 个位置为 \(0\)\(B-1\) 个位置为 \(1\),剩下的为下标减去 \(2\)

于是问题变成了从 \([0,n-2]\) 中选择 \(A+B-2\) 个数删掉,所有方案剩下数之积之和。

\(n\) 个数中选择 \(m\) 个数删掉,似乎是经典的背包问题。

\(f[n][m]\)\([0,n-2]\) 中删掉 \(m\) 个数的积之和。

然后你发现 \(f[1][1]\) 的定义出现了问题,于是重新设,设为 \([0,n-1]\),最后把 \(f[n][A+B-2]\) 改成 \(f[n-1][A+B-2]\) 就行。

这里的递推式为 \(f[n][m]=f[n-1][m] \times (n-1)+f[n-1][m-1]\)

然后我们需要从 \(A+B-2\) 中选择 \(A-1\) 个位置令其为 \(1\),剩下的位置为 \(0\)

所以答案就是 \(f[n-1][A+B-2] \times \binom {A+B-2} {A-1}\)

写完题解后翻了一圈题解区,咋全是斯特林数,然后发现自己的 \(f\) 就是斯特林数/qd

#include<cstdio>
#include<cctype>
typedef unsigned ui;
const ui M=2e5+5,mod=1e9+7;
ui T,n[M],A[M],B[M],C[205][205],f[50005][205];
inline ui Add(const ui&a,const ui&b){
	return a+b>=mod?a+b-mod:a+b;
}
inline ui read(){
	ui n(0);char s;while(!isdigit(s=getchar()));
	while(n=n*10+(s&15),isdigit(s=getchar()));return n;
}
inline void write(ui n){
	static char s[10];ui top(0);
	while(s[++top]=n%10^48,n/=10);
	while(putchar(s[top]),--top);
}
signed main(){
	ui i,j,mx(0);T=read();f[0][0]=C[0][0]=1;
	for(i=1;i<=T;++i)n[i]=read(),A[i]=read(),B[i]=read(),n[i]>mx&&(mx=n[i]);
	for(i=1;i<=mx;++i)for(j=1;j<=i&&j<=200;++j)f[i][j]=(f[i-1][j]*(i-1ull)+f[i-1][j-1])%mod;
	for(i=1;i<=200;++i)for(C[i][0]=1,j=1;j<=200;++j)C[i][j]=Add(C[i-1][j],C[i-1][j-1]);
	for(i=1;i<=T;++i)write(1ull*f[n[i]-1][A[i]+B[i]-2]*C[A[i]+B[i]-2][A[i]-1]%mod),putchar(10);
}

推荐阅读