洛谷2606 BZOJ2111 ZJOI2010 排列计数 树形dp 卢卡斯定理

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/88517265

题目链接

题意:
求长度为 n n 的排列,满足 P i > P i / 2 P_i>P_{i/2} ,其中除是下取整的除法。 n < = 1 e 6 , n<=1e6, 模数 < = 1 e 9 <=1e9

题解:
为什么洛谷把这个题放到数位dp的题里面啊,然后感觉一脸懵逼。

感觉如果线段树写的比较熟练的话,不难看出其中应该有一个 P i < P i 2 , P i < P i 2 + 1 P_{i}<P_{i*2},P_{i}<P_{i*2+1} ,从编号大的连向编号小的,这就是一个树形结构。我们仔细观察一下还会发现,这其实是一个小根堆。

那么现在我们就可以转化一下模型,变成问 n n 个点的小根堆有多少种。我们考虑用dp来计数。我们设 d p [ i ] dp[i] 表示 i i 这个点为根组成的小根堆的方案数。我们考虑先算出每个点为根的堆的点数以及左右子树的点数,我们记为 s i z e [ i ] size[i] 。那么我们在算 i i 这个点的dp值的时候,我们考虑其中两个子树的点数是固定了的,但是我们给根留一个最小节点之后,可以任意分配编号给一个子树,于是方案数应该是 d p [ i ] = C s i z e [ i ] 1 s i z e [ l s o n ] d p [ l s o n ] d p [ r s o n ] dp[i]=C_{size[i]-1}^{size[lson]}*dp[lson]*dp[rson] 。其中组合数的话我们用一个卢卡斯定理处理,但是由于他给的模数只有上限没有下限,于是可能出现模数比 n n 小的情况,所以在处理组合数的时候需要注意一下。最后 d p [ 1 ] dp[1] 就是答案。

这样就做完这个题了。复杂度是 O ( n l u c a s ) O(n*lucas) 的。

代码:

#include <bits/stdc++.h>
using namespace std;

int n;
long long ans,mod,dp[1000010],sz[1000010],jie[1000010],ni[1000010];
inline long long ksm(long long x,long long y)
{
	long long res=1;
	while(y)
	{
		if(y&1)
		res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
inline long long lucas(int x,int y)
{
	if(y>x)
	return 0;
	if(x<mod&&y<mod)
	return jie[x]*ni[y]%mod*ni[x-y]%mod;
	return lucas(x/mod,y/mod)*lucas(x%mod,y%mod)%mod;
}
int main()
{
	scanf("%d%lld",&n,&mod);
	jie[0]=1;
	for(int i=1;i<=min(1ll*n,mod-1);++i)
	jie[i]=jie[i-1]*i%mod;
	ni[min(1ll*n,mod-1)]=ksm(jie[min(1ll*n,mod-1)],mod-2);
	for(int i=min(1ll*n,mod-1)-1;i>=0;--i)
	ni[i]=ni[i+1]*(i+1)%mod;
	for(int i=n;i>=1;--i)
	{
		sz[i]=1;
		if((i<<1)<=n)
		sz[i]+=sz[i<<1];
		if((i<<1|1)<=n)
		sz[i]+=sz[i<<1|1];
		if((i<<1|1)<=n)
		dp[i]=lucas(sz[i]-1,sz[i<<1])*dp[i<<1]%mod*dp[i<<1|1]%mod;
		else if((i<<1)<=n)
		dp[i]=dp[i<<1];
		else
		dp[i]=1;
	}
	printf("%lld\n",dp[1]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/88517265