BZOJ P2111 Perm 排列计数【详细题解】【递推】【组合数学】

OI/ACM之数论数学 同时被 2 个专栏收录
32 篇文章 0 订阅

闲话:

这道题我看到网上很多题解都比较水,不知道到底是因为作者确实认为这几句话足够给读者讲懂还是直接照搬了别人题解自己却又没有懂清楚不知道应该如何讲。

网上很多人把这道题叫做 D P DP DP,其实我觉得这道题并不像是 D P DP DP,而是一种递推,因为这道题当中没有丝毫的状态转移。

题目分析:

我们将每一个数都看作一个点,将需要满足大小关系的两个点连一条边,容易发现全部边的连好后最后我们得到了一棵树,而要满足题目要求,需要满足这棵树是一个小根堆。

举个例子:

P 1 = 1 , P 2 = 3 , P 3 = 2 , P 4 = 6 , P 5 = 5 , P 6 = 4 P1=1,P2=3,P3=2,P4=6,P5=5,P6=4 P1=1,P2=3,P3=2,P4=6,P5=5,P6=4
那么需要满足的大小关系有:
P 2 = 3 > P 1 = 2 P2=3>P1=2 P2=3>P1=2
P 3 = 2 > P 1 = 1 P3=2>P1=1 P3=2>P1=1
P 4 = 6 > P 2 = 3 P4=6>P2=3 P4=6>P2=3
P 5 = 5 > P 2 = 3 P5=5>P2=3 P5=5>P2=3
P 6 = 4 > P 3 = 2 P6=4>P3=2 P6=4>P3=2

我们将需要满足大小关系的连边后得到下面的一棵树(由于一棵树的根节点始终都是相对的,为了方便讨论我们始终将 1 1 1号节点视为根节点):

这里写图片描述
显然,这是一个小跟堆。

接下来我们再考虑定一下状态:

D P [ I ] DP[I] DP[I]表示以 I I I号点作为根节点时满足题意的方案数
显然, D P [ 1 ] DP[1] DP[1]应该是我们最后的输出答案

因为 D P [ 1 ] DP[1] DP[1]才是我们的答案,所以接下来我们考虑倒推。

对于某一个节点 X X X的状态,应该由它的左儿子与右儿子递推回来,于是我们根据题意以及乘法原理,先暂且得到下面的递推关系:

D P [ I ] = D P [ I &lt; &lt; 1 ] ∗ D P [ I &lt; &lt; 1 ∣ 1 ] DP[I]=DP[I&lt;&lt;1]*DP[I&lt;&lt;1|1] DP[I]=DP[I<<1]DP[I<<11]

但是这就对了吗?这是有点问题的,我们依然以上面的例子来说明:
这里写图片描述
如果我们仅凭上面的式子,那么我们应该得到:

D P [ 1 ] = D P [ 2 ] ∗ D P [ 3 ] DP[1]=DP[2]*DP[3] DP[1]=DP[2]DP[3]

但是这样递推似乎没有考虑完全:如果只是简单地将左儿子与右儿子的值乘起来,那么我们得到的只是左儿子即 D P [ 2 ] DP[2] DP[2]以及右儿子即 D P [ 3 ] DP[3] DP[3]中的数保持一定地相对不变乘起来的,也就是左儿子当中的三个数 3 , 6 , 5 3,6,5 3,6,5只是仍然在左子树中交换位置,而在实际当中左子树中的数是可以与右子树中的某些数交换的。、

比如:

s w a p ( P 5 , P 6 ) − &gt; P 5 = 4 , P 6 = 5 swap(P5,P6)-&gt;P5=4,P6=5 swap(P5,P6)>P5=4,P6=5

这仍然是满足条件的,但是

D P [ I ] = D P [ I &lt; &lt; 1 ] ∗ D P [ I &lt; &lt; 1 ∣ 1 ] DP[I]=DP[I&lt;&lt;1]*DP[I&lt;&lt;1|1] DP[I]=DP[I<<1]DP[I<<11]

并没有包含在里面,所以我们似乎在递推式中还应该考虑添加一点东西。

这个时候我们就来考虑一下组合问题了。

由于我们最终只是左子树与右子树当中的数交换位置,也就是左子树的个数与右子树的个数是不变的,这就相当于我们从 M M M个数当中取出 N N N个数,也就是组合数 C M N C^N_M CMN了。

这里也许会有一个小问题,就是按照组合数 C ( N , M ) C(N,M) C(N,M)取出来的数放置后一定满足最后的结果是小跟堆吗?这是肯定的,因为每个数不重复,两两相比一定有一个大小。

于是我们就得到了下面的状态转移了( S i z e [ I ] Size[I] Size[I]表示以 I I I为根节点的子树的大小):
D P [ I ] = D P [ I ∗ 2 ] ∗ D P [ I ∗ 2 + 1 ] ∗ C S i z e [ I ] − 1 S i z e [ I ∗ 2 ] DP[I]=DP[I*2]*DP[I*2+1]*C^{Size[I*2]}_{Size[I]-1} DP[I]=DP[I2]DP[I2+1]CSize[I]1Size[I2]
S i z e [ I ] = S i z e [ I ∗ 2 ] + S i z e [ I ∗ 2 + 1 ] + 1 Size[I]=Size[I*2]+Size[I*2+1]+1 Size[I]=Size[I2]+Size[I2+1]+1
由于 P P P是质数,我们直接用 L u c a s Lucas Lucas处理组合数即可。

关键代码:

for(I=N;I>=1;I--){
	Size[I]=Size[I<<1]+Size[I<<1|1]+1;
	DP[I]=Lucas(Size[I]-1,Size[I<<1]);
	if((I<<1)<=N){
		DP[I]=(DP[I]*DP[I<<1])%P;
	}
	if((I<<1|1)<=N){
		DP[I]=(DP[I]*DP[I<<1|1])%P;
	}
}
  • 3
    点赞
  • 1
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

Yucohny

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值