【题目描述】

求有多少种长度为 $n$ 的序列 $A$,满足以下条件:

  • $1$ ~ $n$ 这 $n$ 个数在序列中各出现了一次
  • 若第 $i$ 个数 $A_i$ 的值为 $i$,则称 $i$ 是稳定的。序列恰好有 $m$ 个数是稳定的

满足条件的序列可能很多,序列数对 $10^9+7$ 取模。

【题目链接】

【SDOI 2016 Day2 T2】 BZOJ 4517 COGS 2224

【解题思路】

首先我们来考虑这样一种思路,首先选出这 $m$ 个位置,方案数为 $C_n^m$,然后剩下的位置作全排列,方案数为 $(n - m)!$,根据乘法原理,两者乘起来即可。

可惜这样是不行的,因为我们剩下的位置是随意作排列,这些位置中也可能出现稳定的数,换句话说,我们求出的是至少有$m$个数稳定的方案数,那怎么办呢,常见的思路就是用至少有 $m$ 个的方案数,减去至少有 $m + 1$个的方案数,即可得到恰好有 $m$ 个的方案数。

还有更直接的思路,要求剩下的位置排列时不得出现稳定数——这正是经典问题:错排

错排公式: $$ f_i = (i - 1)(f_{i - 1} + f_{i - 2}) $$

边界: $$ f_0 = 1, f_1 = 0 $$

本题答案即为: $$ ans = C_n^mf_{n - m} $$

预处理阶乘 + 预处理阶乘逆元 + 预处理错排 = $O(1)$ 回答

【AC代码】

IO优化 好给力,~~暂居BZOJ本题榜首~~,现在没有啦...

#include <cstdio>

namespace IO{
    const int IN_SIZE = 1 << 15, OUT_SIZE = 1 << 20;
    char inBuf[IN_SIZE], *S, *T;
    char outBuf[OUT_SIZE];
    int now;

    inline void init(){
        S = T = inBuf;
        now = 0;
    }

    inline char getchar(){
        if(S == T) S = inBuf, T = inBuf + fread(inBuf, 1, IN_SIZE, stdin);
        if(S == T) return EOF;
        return *S++;
    }

    inline void readint(int &x){
        char ch;
        do ch = getchar(); while(ch < '0' || ch > '9');
        x = ch ^ '0';
        for(ch = getchar(); ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + (ch ^ '0');
    }

    inline void putAll(){
        if(now) fwrite(outBuf, 1, now, stdout), now = 0;
    }

    inline void putchar(char ch){
        outBuf[now++] = ch;
        if(now == OUT_SIZE) putAll();
    }

    inline void putInt(int x){
        if(x > 9) putInt(x / 10);
        putchar(x % 10 + '0');
    }

    inline void end(){
        if(now) putAll();
    }
};

#define MAXN 1000000
#define MOD 1000000007

typedef long long int64;

inline int mulMod(int a, int b){
    return (int64) a * b % MOD;
}

void exgcd(int a, int b, int d, int &x, int &y) {
    if (!b) d = a, x = 1, y = 0;
    else exgcd(b, a % b, d, y, x), y -= x * (a / b);
}

inline int inv(int num){
    int d, x, y;
    exgcd(num, MOD, d, x, y);
    return ((x % MOD) + MOD) % MOD;
}

int facMod[MAXN + 1], facInv[MAXN + 1], f[MAXN + 1];

inline void init(){
    int n = MAXN;

    facMod[0] = 1;
    for(int i = 1; i <= n; i++) facMod[i] = mulMod(facMod[i - 1], i);

    facInv[n] = inv(facMod[n]);
    for(int i = n; i; i--) facInv[i - 1] = mulMod(facInv[i], i);

    f[0] = 1, f[1] = 0;
    for(int i = 2; i <= n; i++) f[i] = mulMod(i - 1, (f[i - 2] + f[i - 1]) % MOD);
}

inline int cMod(int n, int k){
    return mulMod(facMod[n], mulMod(facInv[n - k], facInv[k]));
}

int main(){
    IO::init();

    int T;

    init();
    scanf("%d", &T);
    for(int i = 0; i < T; i++){
        int n, m;
        IO::readint(n), IO::readint(m);
        IO::putInt(mulMod(cMod(n, m), f[n - m]));
        IO::putchar('\n');
    }

    IO::end();

    fclose(stdin), fclose(stdout);
    return 0;
}

就是这样啦