BZOJ 2111: [ZJOI2010]Perm 排列計數(簡單組合數學)
阿新 • • 發佈:2019-02-13
2111: [ZJOI2010]Perm 排列計數
問題等價於求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 , 費馬小定理求乘法逆元;
組合計數
求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[n]*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; }