T3. 挑剔的美食家
作为一名挑剔的美食家,小猴对食物是很讲究的,哪怕摆在面前的只有若干香蕉和苹果,小猴依然有他的讲究。
已知目前已有 \(n\) 根香蕉和 \(m\) 个苹果,小猴制定了以下规则来决定自己的食用顺序:
- 每个香蕉都被认为是独特的个体,可以理解为编号为 \(1 \sim n\) 的香蕉各不同
- 每个苹果之间没有区别
- 每个时刻小猴只食用一个水果
由于小猴更喜欢香蕉,因此要求在吃第 \(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;
}