错排问题

源代码:

#include<cstdio>
#define LL long long
#define INF 1000000007
LL m,n,Num(0),i[100001],Sum[100001];
bool Vis1[100001]={0},Vis2[100001]={0};
LL M(LL t) //取模,注意负数。
{
    if (t>=0&&t<INF)
      return t;
    t%=INF;
    if (t<0)
      t+=INF;
    return t;
}
LL Count(LL X,LL S) //快速幂。
{
    LL Number=1;
    while (S)
    {
        if (S&1)
          Number=(Number*X)%INF;
        X=(X*X)%INF;
        S>>=1;
    }
    return Number;
}
LL C(LL n,LL m)
{
    if (n<m||!n)
      return 0;
    return i[n]*Sum[n-m]%INF*Sum[m]%INF;
}
int main()
{
    scanf("%lld%lld",&n,&m);
    i[0]=1;
    for (LL a=1;a<=n;a++)
      i[a]=i[a-1]*a%INF;
    for (LL a=0;a<=n;a++)
      Sum[a]=Count(i[a],INF-2);
    for (LL a=1;a<=m;a++)
    {
        LL t1,t2;
        scanf("%lld%lld",&t1,&t2);
        if (t1==t2)
        {
            printf("0");
            return 0;
        }
        Vis1[t1]=Vis2[t2]=true;
    }
    LL Ans=i[n-m];
    for (LL a=1;a<=n;a++)
      if (!Vis1[a]&&!Vis2[a])
        Num++;
    for (LL a=1;a<=Num;a++)
      if (a&1)
        Ans=M(Ans-C(Num,a)*i[n-m-a]);
      else
        Ans=(Ans+C(Num,a)*i[n-m-a])%INF;
    printf("%lld",Ans);
    return 0;
}

/*
    一道NOIP阶段比较全面的排列组合题。
    原始的错排公式:
        (1)通过CodeVS ⑨要写信 可以得出递推公式:
            f[i]=(i-1)(f[i-1]+f[i-2])
        (2)n个数的全排列个数为n!,其中第k位为k的个数为(n-1)!,那么对于n个数,共有n(n-1)!为放对一个的,则减去;
           但会把两个数放对的多减一次,则加上C(n,2)*(n-2)!;
           但会把三个数放对的多加一次,则减去C(n,3)*(n-3)!;
           以此类推,最终可得错排公式:
                Ans=n!-C(n,1)*(n-1)!+C(n,2)*(n-2)!-C(n,3)*(n-3)!+...+(-1)^n*C(n,n)*(n-n)!
                   =∑(k=0~n)(-1)^k*C(n,k)*(n-k)!
           其实就是容斥原理。
    现行的错排公式:
        仿照以上,设s为剩下的(n-m)个数仍有可能放到正确位置的个数,则有:
            Ans=(n-m)!-C(s,1)*(n-m-1)!+C(s,2)*(n-m-2)!+...+(-1)^s*C(s,s)*(n-m-s)!
               =∑(k=0~s)(-1)^k*C(s,k)*(n-m-k)!
        排列组合如此神奇。
*/
原文地址:https://www.cnblogs.com/Ackermann/p/6037356.html