bzoj2111【ZJOI2010】Perm 排列计数
2111: [ZJOI2010]Perm 排列计数
Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 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]); }