您的位置:首页 > 其它

BZOJ 2111: [ZJOI2010]Perm 排列计数(简单组合数学)

2013-01-02 20:29 465 查看
2111: [ZJOI2010]Perm 排列计数

组合计数

求1-n排列中对于任意2<=i<=N 有 Pi>P(i/2) 的排列个数 mod 质数 p 的余数;

问题等价于求1~n组成一棵满足小根堆性质的完全二叉树的方案数;

定义f[i]为当这棵完全二叉树有i个节点时的方案数;

则 f[i] = C(i-1,left) * f[left] * f[i-1-left];

f[0] = f[1] = 1;

下面来求left

深度 - 1 = floor(log2(i));

记 tot = 前深度 - 1 层总结点数 = pow(2,深度-1)-1;

则 left = (tot-1)/2 + min(i-tot,(tot+1)/2); 

下面来求C(n,k) mod p 

直接 预处理 n! mod p , 费马小定理求乘法逆元; 
 

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
using namespace std;
int fac[1000010],f[1000010];
int n,p;
inline int pow(int x,int y,int p)
{
int res(1);
x %= p;
while(y)
{
if(y&1) res = (long long)res * x % p;
x = (long long)x * x % p;
y >>= 1;
}
return res;
}
inline int C(int n,int m,int p)
{
return (long long)fac
*pow((long long)fac[m]*fac[n-m]%p,p-2,p) % p;
}
inline int solve(int x)
{
if(f[x]) return f[x];
int depth = log2(x);
int tot = pow(2,depth,10000000)-1;
int left = (tot-1>>1) + min(x - tot,tot+1>>1);
return f[x] = (long long)C(x-1,left,p)*solve(left)%p*solve(x-left-1)%p;
}
int main()
{
scanf("%d%d",&n,&p);
fac[0] = 1;
for(int i = 1; i <= n; i++)
fac[i] = (long long)fac[i-1] * i % p;
f[0] = f[1] = 1;
printf("%d\n",solve(n));
return 0;
}


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: