[BZOJ4517] [Sdoi2016]排列计数

题目描述

Description

求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。

Input

第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行两个整数 n、m。
T=500000,n≤1000000,m≤1000000

Output

输出 T 行,每行一个数,表示求出的序列数

Sample Input

5
1 0
1 1
5 2
100 50
10000 5000

Sample Output

0
1
20
578028887
60695423

题目分析

首先我们在这个数列里随机选出n个数 让他们在自己的位置上 并使得剩下的n-m个数错排

令f[i]为i个数错排的方案数

然后就是错排公式的推导

假设有n封信,第一封信可放在(2-n)的任一个信封里,共n-1种放法,设第一封信放在了第k个信封里,若此时第k封信放在了第1个信封里,则只要将剩下的n-2错排,即f(n-2),若第k封信没有放在了第1个信封里,可将第1封信的位置看成是“第k个位置”,即将n-1封信错排,即为f(n-1)

由递推可得,f(n)=(n-1)*(f(n-1)+f(n-2))

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std;
long long f[1001010];
int n,m,t; 
long long mod=1000000007ll;
long long jie[1001010];
long long yuan[1001010];
long long ksm(long long x,long long y)
{
    long long sum=1;
    while(y)
    {
        if(y&1) sum=(sum*x)%mod;
        y>>=1,x=(x*x)%mod;
    }
    return sum;
}
void init()
{
    f[0]=1,f[1]=0,f[2]=1;
    for(int i=3;i<=1000000;i++) 
        f[i]=(i-1)*(f[i-1]+f[i-2])%mod;
    jie[0]=1;
    for(int i=1;i<=1000000;i++) jie[i]=(jie[i-1]*i)%mod;
    yuan[1000000]=ksm(jie[1000000],mod-2);
    for(int i=1000000-1;i>=0;i--) yuan[i]=(yuan[i+1]*(i+1))%mod;
}
long long get(int x,int y)
{
    return ((jie[x]*yuan[y])%mod*yuan[x-y])%mod;
}
int main()
{
    scanf("%d",&t);
    init();
    while(t--)
    {
        scanf("%d%d",&n,&m);
        printf("%lld\n",get(n,m)*f[n-m]%mod);
    }
    return 0;
}

发表评论

邮箱地址不会被公开。 必填项已用*标注