【组合数学】 P4071 排列计数

121 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

[SDOI2016]排列计数

题目描述

求有多少种 11nn 的排列 aa,满足序列恰好有 mm 个位置 ii,使得 ai=ia_i = i

答案对 109+710^9 + 7 取模。

输入格式

本题单测试点内有多组数据

输入的第一行是一个整数 TT,代表测试数据的整数。

以下 TT 行,每行描述一组测试数据。

对于每组测试数据,每行输入两个整数,依次代表 nnmm

输出格式

共输出 TT 行,对于每组测试数据,输出一行一个整数代表答案。

样例 #1

样例输入 #1

5
1 0
1 1
5 2
100 50
10000 5000

样例输出 #1

0
1
20
578028887
60695423

提示

数据规模与约定

本题共 20 个测试点,各测试点等分,其数据规模如下表。

测试点编号T=T =n,mn, m \leq测试点编号T=T =n,mn, m \leq
131\sim 310310^388101210 \sim 1210310^310310^3
464 \sim 610310^31212131413 \sim 145×1055 \times 10^510310^3
797 \sim 910310^3100100152015 \sim 205×1055 \times 10^510610^6

对于全部的测试点,保证 1T5×1051 \leq T \leq 5 \times 10^51n1061 \leq n \leq 10^60m1060 \leq m \leq 10^6

解题思路

  • 组合数的求法,用阶乘+逆元预处理求组合数,先预处理出数据范围内的阶乘,最后O(1)O(1)回答。

  • 对于错排列的求法,有这么一个递推公式D[i]=(i1)(D[i1]+D[i2])D[i]=(i-1)*(D[i-1]+D[i-2])

#include <iostream>
#include <cstdio>
using namespace std;

typedef long long ll;
const ll mod=1000000007;
ll f[1000000+50],inv[1000000+50],d[1000000+50];
ll T;
ll n,m;

ll q_pow(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1){
            ans=ans*a%mod;
        }
        b>>=1;
        a=a*a%mod;
    }
    return ans%mod;
}

inline void init(){
    f[0]=f[1]=d[0]=d[2]=inv[0]=inv[1]=1;f[2]=2;inv[2]=q_pow(f[2],mod-2);
}

inline void prework(){
    for(ll i=3;i<=1000000;++i){
        f[i]=f[i-1]*i%mod;
        inv[i]=q_pow(f[i],mod-2);
        d[i]=(i-1)*(d[i-1]+d[i-2])%mod;
    }
}

inline ll ask(ll n,ll m){
    return (f[n]*inv[m]%mod*inv[n-m]%mod)%mod;
}

inline ll read(){
    ll ans;
    char c;
    while(c=getchar(),c<'0'||c>'9'){
        
    }
    ans=c-'0';
    while(c=getchar(),c>='0'&&c<='9'){
        ans=ans*10+c-'0';
    }
    return ans;
}

int main(){
    init();
    prework();
    scanf("%lld",&T);
    while(T--){
        n=read();m=read();
        printf("%lld\n",ask(n,m)*d[n-m]%mod);
    }
    
    return 0;
}