T3. 挑剔的美食家

作为一名挑剔的美食家,小猴对食物是很讲究的,哪怕摆在面前的只有若干香蕉和苹果,小猴依然有他的讲究。
已知目前已有 \(n\) 根香蕉和 \(m\) 个苹果,小猴制定了以下规则来决定自己的食用顺序:

  1. 每个香蕉都被认为是独特的个体,可以理解为编号为 \(1 \sim n\) 的香蕉各不同
  2. 每个苹果之间没有区别
  3. 每个时刻小猴只食用一个水果

由于小猴更喜欢香蕉,因此要求在吃第 \(i\) 个苹果之前必须已经至少吃了 \(i+k\) 个香蕉。(\(k\) 可能是负数)并对结果模 \(10^9 + 7\)

小猴现在希望知道,满足条件的前提下,有多少种不同的食用顺序方案。
(注:两个方案是不同的,当且仅当有至少一个时刻食用了不同的水果或者不同编号的香蕉)

限制:

  • 对于 \(10\%\) 的数据:\(1 \leqslant n, m \leqslant 6\)
  • 对于 \(20\%\) 的数据:\(1 \leqslant n, m \leqslant 10\)
  • \(1 \leqslant n, m \leqslant 1000\)
  • \(-n \leqslant k \leqslant n\)

算法分析

部分分1:暴搜,用回溯法得到每个香蕉的排列

代码实现
#include <bits/stdc++.h>

using namespace std;

int n, m, k, ans;
bool vis[15];

void dfs(int a, int b) {
    if (a > n or b > m) return;
    if (a == n and b == m) {
        ans++;
        return;
    }
    for (int i = 1; i <= n; ++i) {
        if (vis[i]) continue;
        vis[i] = 1;
        dfs(a+1, b);
        vis[i] = 0;
    }
    if (a > b+k) dfs(a, b+1);
}

int main() {
    cin >> n >> m >> k;
    
    dfs(0, 0);
    
    cout << ans << '\n';
    
    return 0;
}

部分分2:计数原理
香蕉排列顺序:1. 无序版方案 2. 内部排列:分步计数,乘法原理
那么答案就是无序列版方案的 \(ans \times n!\)

代码实现
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

//const int mod = 998244353;
const int mod = 1000000007;
struct mint {
    ll x;
    mint(ll x=0):x((x%mod+mod)%mod) {}
    mint operator-() const {
        return mint(-x);
    }
    mint& operator+=(const mint a) {
        if ((x += a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator-=(const mint a) {
        if ((x += mod-a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator*=(const mint a) {
        (x *= a.x) %= mod;
        return *this;
    }
    mint operator+(const mint a) const {
        return mint(*this) += a;
    }
    mint operator-(const mint a) const {
        return mint(*this) -= a;
    }
    mint operator*(const mint a) const {
        return mint(*this) *= a;
    }
    mint pow(ll t) const {
        if (!t) return 1;
        mint a = pow(t>>1);
        a *= a;
        if (t&1) a *= *this;
        return a;
    }

    // for prime mod
    mint inv() const {
        return pow(mod-2);
    }
    mint& operator/=(const mint a) {
        return *this *= a.inv();
    }
    mint operator/(const mint a) const {
        return mint(*this) /= a;
    }
};
istream& operator>>(istream& is, mint& a) {
    return is >> a.x;
}
ostream& operator<<(ostream& os, const mint& a) {
    return os << a.x;
}

int n, m, k;
mint ans;
bool vis[15];

void dfs(int a, int b) {
    if (a > n or b > m) return;
    if (a == n and b == m) {
        ans += 1;
        return;
    }
    dfs(a+1, b);
    if (a > b+k) dfs(a, b+1);
}

int main() {
    cin >> n >> m >> k;
    
    dfs(0, 0);
    
    for (int i = 1; i <= n; ++i) {
        ans *= i;
    }
    
    cout << ans << '\n';
    
    return 0;
}

满分做法:

dp[i][j] 表示吃了 \(i\) 个香蕉,\(j\) 个苹果的方案数(无序)

转移方程:

\( dp[i][j] = dp[i-1][j] + dp[i][j-1] \)

初值:

  • \(dp[i][0] = 1\)
  • \(dp[0][j] = 1\), 当 \(0-j \geqslant k\)

最后的答案为 \(dp[n][m] \times n!\)

代码实现
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

//const int mod = 998244353;
const int mod = 1000000007;
struct mint {
    ll x;
    mint(ll x=0):x((x%mod+mod)%mod) {}
    mint operator-() const {
        return mint(-x);
    }
    mint& operator+=(const mint a) {
        if ((x += a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator-=(const mint a) {
        if ((x += mod-a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator*=(const mint a) {
        (x *= a.x) %= mod;
        return *this;
    }
    mint operator+(const mint a) const {
        return mint(*this) += a;
    }
    mint operator-(const mint a) const {
        return mint(*this) -= a;
    }
    mint operator*(const mint a) const {
        return mint(*this) *= a;
    }
    mint pow(ll t) const {
        if (!t) return 1;
        mint a = pow(t>>1);
        a *= a;
        if (t&1) a *= *this;
        return a;
    }

    // for prime mod
    mint inv() const {
        return pow(mod-2);
    }
    mint& operator/=(const mint a) {
        return *this *= a.inv();
    }
    mint operator/(const mint a) const {
        return mint(*this) /= a;
    }
};
istream& operator>>(istream& is, mint& a) {
    return is >> a.x;
}
ostream& operator<<(ostream& os, const mint& a) {
    return os << a.x;
}

int n, m, k;
mint dp[1005][1005];
mint ans;

int main() {
    cin >> n >> m >> k;
    
    for (int i = 0; i <= n; ++i) dp[i][0] = 1;
    for (int j = 0; j <= m and 0-j >= k; ++j) dp[0][j] = 1;
    
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m and i-j >= k; ++j) {
            dp[i][j] = dp[i-1][j] + dp[i][j-1];
        }
    }
    
    ans = dp[n][m];
    for (int i = 1; i <= n; ++i) {
        ans *= i;
    }
    
    cout << ans << '\n';
    
    return 0;
}