1. 程式人生 > >【[SDOI2016]排列計數】

【[SDOI2016]排列計數】

一眼題,答案就是\(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;
}