【[SDOI2016]排列計數】
阿新 • • 發佈:2019-01-02
一眼題,答案就是\(C_m^m*d_{n-m}\)
就是從\(n\)箇中選取\(m\)個在位,剩下的錯排,之後就是乘法原理了
但是我發現我的錯排公式竟然一直不會推
這個遞推式很簡單,就是\(d[1]=0,d[2]=1,d[n]=(n-1)*(d[n-2]+d[n-1)\)
其實是這樣推出來的
我們從\(n\)個元素錯排開始考慮,我們特殊判斷一下第一個位置不能填\(1\),但是從\(2\)到\(n\)這\(n-1\)個數可以隨便選,於是有\(n-1\)種可能
假設第一次放的的元素是\(k\)
之後剩下的就是
\[1\ 2\ 3\ ...k-1\ \ k+1\ \ k+2\ \ k+3...n\]
我們可以將這些從小到大對應到\(1\)到\(n-1\),之後剩下的繼續錯排就好啦
於是就是\(d[n-1]\)
但是我們這個樣子本質上是使得\(k\)那個位置不能放\(k+1\)的(因為\(k+1\)在去掉\(k\)之後是第\(k\)小的),於是我們還可以讓\(k\)這個位置放\(k+1\),之後剩下的繼續錯排,於是就是\(d[n-2]\)
加法原理這兩種不同的情況加起來,再利用乘法原理第一位上有\(n-1\)種選擇
於是就有\(d[n]=(n-1)*(d[n-2]+d[n-1])\)
發現luogu日報裡竟然又講錯排那就在這裡收藏一下
這道題的程式碼
#include<iostream> #include<cstring> #include<cstdio> #define re register #define LL long long #define maxn 1000005 const int mod=1e9+7; LL fac[maxn],d[maxn]; int T; LL x,y; inline LL read() { char c=getchar(); LL x=0; while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar(); return x; } LL exgcd(LL a,LL b,LL &x,LL &y) { if(!b) return x=1,y=0,a; LL r=exgcd(b,a%b,y,x); y-=a/b*x; return r; } inline LL C(LL n,LL m) { LL r=exgcd(fac[m]*fac[n-m]%mod,mod,x,y); x=(x%mod+mod)%mod; return fac[n]*x%mod; } int main() { T=read(); fac[0]=1,fac[1]=1; for(re int i=2;i<=1000000;i++) fac[i]=fac[i-1]*i%mod; d[0]=1,d[1]=0,d[2]=1; for(re int i=3;i<=1000000;i++) d[i]=(d[i-1]+d[i-2]%mod)*(i-1)%mod; LL n,m; while(T--) { n=read(),m=read(); printf("%lld",C(n,m)*d[n-m]%mod); putchar(10); } return 0; }