#笛卡尔树,树形dp#洛谷 6453 [COCI 2008/2009 #4] PERIODNI

题目传送门


分析

考虑建立笛卡尔树,键值满足小根堆的性质,那么相当于左右两棵子树递归处理,

当前处理 \(\min\{a_{l\sim r}\}\) 这些行的填数情况,设左子树选了 \(j\) 列,右子树选了 \(k\) 列,

\(dp[x][i]\) 表示以 \(x\) 为根的笛卡尔树选了 \(i\) 列的方案数,

那么 \(dp[x][i]=\sum dp[ls[x]][j]*dp[rs[x]][k]]*\binom{h[x]-h[fat[x]]}{i-j-k}*\binom{r-l+1-j-k}{i-j-k}*(i-j-k)!\)

发现可以将前面的部分卷积成 \(f[x][j+k]\) 再和后面拼接,这样复杂度就做到 \(O(nk^2)\)


代码

#include <cstdio>
#include <cctype>
using namespace std;
const int N=511,M=1000011,mod=1000000007;
int st[N],a[N],n,m,L[N],R[N],Top,root,dp[N][N],f[N][N],fac[M],inv[M];
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
int mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int C(int n,int m){return n<m?0:1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
void dfs(int x,int l,int r,int h){
	dp[x][0]=f[x][0]=1;
	if (!x) return;
	dfs(L[x],l,x-1,a[x]),dfs(R[x],x+1,r,a[x]);
	for (int i=1;i<=m;++i)
	for (int j=0;j<=i;++j)
	    f[x][i]=mo(f[x][i],1ll*dp[L[x]][j]*dp[R[x]][i-j]%mod);
	for (int i=1;i<=m;++i)
	for (int j=0;j<=i;++j)
	    dp[x][i]=mo(dp[x][i],1ll*C(a[x]-h,i-j)*C(r-l+1-j,i-j)%mod*fac[i-j]%mod*f[x][j]%mod);
}
int main(){
	n=iut(),m=iut(),a[0]=-1,fac[0]=fac[1]=inv[0]=inv[1]=1;
	for (int i=2;i<M;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for (int i=2;i<M;++i) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*inv[i-1]*inv[i]%mod;
	for (int i=1;i<=n;++i) a[i]=iut();
	for (int i=1,lst=0;i<=n;++i){
		while (a[st[Top]]>a[i]) --Top;
		if (Top<lst) L[i]=st[Top+1];
		if (Top) R[st[Top]]=i;
		st[lst=++Top]=i;
	}
	dfs(root=st[1],1,n,0);
	return !printf("%d",dp[root][m]);
}
posted @ 2025-06-20 12:54  lemondinosaur  阅读(15)  评论(0)    收藏  举报