「题解」:[组合数学]:Perm 排列计数
題干:
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,?, n的排列中, Magic排列的個數模 p的值。
Sample Input
20 23
Sample Output
16
HINT
100%的數據中,1 ≤ N ≤ 106, P≤ 10^9,p是一個質數。
題目概述:求N個數的排列中滿足P[i]>P[i/2]的個數。
?
?
題解:
拿到這道題一臉懵比,自己想了半天妄圖用純組合數學知識做出來。
問了問大佬,大佬說要用小根堆,嚇得我直接就不是人了。
研究了一下,發現這道題其實就是求n個數組成的小根堆的個數。
寫出來一個狀態轉移方程(搞得跟樹歸似的嚇死個人):f[i]=f[i<<1]+f[i<<1|1]+C(size[i-1],size[i<<1]);
解釋一下:上式中,size代表小根堆(其實就是一個樹型的)以某一點為根節點的子樹的大小。
f代表以當前節點為根節點的小根堆共有多少中排列方式。
$C_{size[i-1]}^{size[i<<1]}$代表的意義是:從比i大的數字中選出左兒子需要的個數插入到左子樹中組成的一種排列。
其實$C_{size[i-1]}^{size[i<<1]}$和$C_{size[i-1]}^{size[i<<1|1]}$還是一樣的。
size的累加過程:siz[i]=siz[i<<1]+siz[i<<1|1]+1;
我們發現,n的范圍還是不小的(10的6次方),所以用到了Lucas定理。就這樣啦~
代碼:
?
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<cmath> #include<cstdlib> using namespace std; long long n,p,siz[4000006],dp[4000006]; long long fac[40000006]; inline long long qpow(long long a,long long b) {register long long ans=1;a%=p;while(b){if(b&1)ans=ans*a%p;a=a*a%p;b>>=1;}return ans; } inline long long C(long long nn,long long k) {if(k>nn)return 0;elsereturn fac[nn]*(qpow(fac[k]*fac[nn-k]%p,p-2))%p; } inline long long Lucas(long long a,long long b) {if(b==0)return 1;return C(a%p,b%p)*Lucas(a/p,b/p)%p; } inline void getchart() {fac[1]=fac[0]=1;for(register long long i=2;i<=n;i++)fac[i]=(fac[i-1]*i)%p;return ; } int main() {scanf("%lld %lld",&n,&p);getchart(); // cout<<fac[10]<<endl;for(register int i=n;i>=1;--i){siz[i]=siz[i<<1]+siz[i<<1|1]+1;dp[i]=Lucas(siz[i]-1,siz[i<<1]);if((i<<1)<=n)dp[i]=(dp[i]*dp[i<<1])%p;if((i<<1|1)<=n)dp[i]=(dp[i]*dp[i<<1|1])%p;}printf("%lld\n",dp[1]);return 0; } 代碼在這里?
轉載于:https://www.cnblogs.com/xingmi-weiyouni/p/11131530.html
總結
以上是生活随笔為你收集整理的「题解」:[组合数学]:Perm 排列计数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 马斯克旗下脑机接口公司Neuralink
- 下一篇: 机械师曙光 16 Pro 笔记本电脑上新