bzoj2111【ZJOI2010】Perm 排列计数

2111: [ZJOI2010]Perm 排列计数

Time Limit: 10 Sec   Memory Limit: 259 MB
Submit: 1548   Solved: 321
[ Submit][ Status][ Discuss]

Description

称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值

Input

输入文件的第一行包含两个整数 n和p,含义如上所述。

Output

输出文件中仅包含一个整数,表示计算1,2,⋯, ���的排列中, Magic排列的个数模 p的值。

Sample Input

20 23

Sample Output

16

HINT

100%的数据中,1 ≤ ��� N ≤ 106, P��� ≤ 10^9,p是一个质数。 数据有所加强

Source




可以发现这n个点构成了一个树形结构,其中1是根节点,i的左二子是i<<1,右儿子是i<<1|1。

我们就可以用动态规划

设f[i]表示以i为根的子树的方案数,s[i]为子树大小。

则f[i]=f[i<<1]*f[i<<1|1]*c(s[i]-1,s[i<<1]),最终答案为f[1]。

现在问题转化为如何求c(s[i]-1,s[i<<1])。注意到p是质数,所以预处理阶乘和阶乘的逆元+Lucas定理解决。





#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define maxn 1000005
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
ll n,m,p;
ll s[maxn],f[maxn],fac[maxn],inv[maxn];
inline ll c(int n,int m)
{
	if (n<m) return 0;
	if (n<p&&m<p) return fac[n]*inv[m]%p*inv[n-m]%p;
	return c(n/p,m/p)*c(n%p,m%p)%p;
}
int main()
{
	n=read();p=read();
	fac[0]=1;
	F(i,1,n) fac[i]=fac[i-1]*i%p;
	inv[0]=inv[1]=1;
	F(i,2,n) inv[i]=(p/i+1)*inv[i-p%i]%p;
	F(i,2,n) inv[i]=inv[i]*inv[i-1]%p;
	D(i,n,1)
	{
		s[i]=s[i<<1]+s[i<<1|1]+1;
		f[i]=((i<<1)>n?1:f[i<<1])*((i<<1|1)>n?1:f[i<<1|1])%p*c(s[i]-1,s[i<<1])%p;
	}
	printf("%lld\n",f[1]);
}


你可能感兴趣的:(dp,Lucas定理,bzoj)