版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/88517265
题意:
求长度为
的排列,满足
,其中除是下取整的除法。
模数
题解:
为什么洛谷把这个题放到数位dp的题里面啊,然后感觉一脸懵逼。
感觉如果线段树写的比较熟练的话,不难看出其中应该有一个 ,从编号大的连向编号小的,这就是一个树形结构。我们仔细观察一下还会发现,这其实是一个小根堆。
那么现在我们就可以转化一下模型,变成问 个点的小根堆有多少种。我们考虑用dp来计数。我们设 表示 这个点为根组成的小根堆的方案数。我们考虑先算出每个点为根的堆的点数以及左右子树的点数,我们记为 。那么我们在算 这个点的dp值的时候,我们考虑其中两个子树的点数是固定了的,但是我们给根留一个最小节点之后,可以任意分配编号给一个子树,于是方案数应该是 。其中组合数的话我们用一个卢卡斯定理处理,但是由于他给的模数只有上限没有下限,于是可能出现模数比 小的情况,所以在处理组合数的时候需要注意一下。最后 就是答案。
这样就做完这个题了。复杂度是 的。
代码:
#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;
}