闲话:
这道题我看到网上很多题解都比较水,不知道到底是因为作者确实认为这几句话足够给读者讲懂还是直接照搬了别人题解自己却又没有懂清楚不知道应该如何讲。
网上很多人把这道题叫做DP,其实我觉得这道题并不像是DP,而是一种递推,因为这道题当中没有丝毫的状态转移。
题目分析:
我们将每一个数都看作一个点,将需要满足大小关系的两个点连一条边,容易发现全部边的连好后最后我们得到了一棵树,而要满足题目要求,需要满足这棵树是一个小根堆。
举个例子:
P1=1,P2=3,P3=2,P4=6,P5=5,P6=4
那么需要满足的大小关系有:
P2=3>P1=2
P3=2>P1=1
P4=6>P2=3
P5=5>P2=3
P6=4>P3=2
我们将需要满足大小关系的连边后得到下面的一棵树(由于一棵树的根节点始终都是相对的,为了方便讨论我们始终将1号节点视为根节点):
显然,这是一个小跟堆。
接下来我们再考虑定一下状态:
DP[I]表示以I号点作为根节点时满足题意的方案数
显然,DP[1]应该是我们最后的输出答案
因为DP[1]才是我们的答案,所以接下来我们考虑倒推。
对于某一个节点X的状态,应该由它的左儿子与右儿子递推回来,于是我们根据题意以及乘法原理,先暂且得到下面的递推关系:
DP[I]=DP[I<<1]*DP[I<<1|1]
但是这就对了吗?这是有点问题的,我们依然以上面的例子来说明:
如果我们仅凭上面的式子,那么我们应该得到:
DP[1]=DP[2]*DP[3]
但是这样递推似乎没有考虑完全:如果只是简单地将左儿子与右儿子的值乘起来,那么我们得到的只是左儿子即DP[2]以及右儿子即DP[3]中的数保持一定地相对不变乘起来的,也就是左儿子当中的三个数3,6,5只是仍然在左子树中交换位置,而在实际当中左子树中的数是可以与右子树中的某些数交换的。、
比如:
swap(P5,P6)->P5=4,P6=5
这仍然是满足条件的,但是
DP[I]=DP[I<<1]*DP[I<<1|1]
并没有包含在里面,所以我们似乎在递推式中还应该考虑添加一点东西。
这个时候我们就来考虑一下组合问题了。
由于我们最终只是左子树与右子树当中的数交换位置,也就是左子树的个数与右子树的个数是不变的,这就相当于我们从M个数当中取出N个数,也就是组合数C(N,M)了。
这里也许会有一个小问题,就是按照组合数C(N,M)取出来的数放置后一定满足最后的结果是小跟堆吗?这是肯定的,因为每个数不重复,两两相比一定有一个大小。
于是我们就得到了下面的状态转移了(Size[I]表示以I为根节点的子树的大小):
由于P是质数,我们直接用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;
}
}