[AGC002F] Leftmost Ball 做题体验
没脑子选手认为是个有意思的题目。
当然,这种神仙题对我来说不看题解做出来是完全不可能的。
看到题目的要求和限制,不难发现一个合法的序列满足它的前缀颜色个数小于等于白球的个数。
这个东西显然不难理解,如果初始前缀中一种颜色的球只出现了一次,那么它就是白球。
出现多次时,才对颜色个数造成贡献。
发现某种颜色第一个球为白色的设定非常难搞,考虑把白球和其他球分开讨论。
为了避免出现重复情况以及其他不可描述的奇怪错误,我们考虑从左到右第一个空着的位置。
接着我们定义这样的 \(\tt dp\) 状态 :
\(\tt f[i][j]\) 表示此时已经放了 \(i\) 个白球和 \(j\) 种颜色所有的球的方案数。
很显然现在可以从两种状态进行转移:\(\tt f[i-1][j]\) 和 \(\tt f[i][j-1]\) 。
先看第一种情况,这种情况是很简单的,因为只考虑第一个空着的位置所以方案已经固定。
再来看第二种情况,还是比较有意思的:
- 首先第一步,我需要从剩下的 \(n-j+1\) 个颜色中选择一个颜色进行摆放,所以这个少不了。
- 接下来就是看有多少种不同的摆放方式:首先因为我们当前第一个空位必须要填,而且一种颜色能显色说明之前一定有一个白球,所以我们只需要考虑 \(k-2\) 个球的摆放方式。
- 至于有多少个空可以填这 \(k-2\) 个球,首先一共有 \(nk\) 个格子,放白球用了 \(i\) 个,放 \(j-1\) 种颜色的球用了 \((j-1)\times(k-1)\) 个球,然后还有你固定的球要减一。
所以状态转移方就出来了:
\[f_{i,j}=f_{i-1,j}+f_{i,j-1}\times(n-j+1)\dbinom{nk-i-(j-1)(k-1)-1}{k-2}
\]
代码也就很好写了:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
#define Enter putchar('\n')
#define quad putchar(' ')
#define int long long
const int N = 2005;
const int mod = 1e9 + 7;
int n, k, fac[N * N], ifac[N * N], f[N][N];
inline int C(int, int);
inline int power(int, int);
signed main(void) {
std::cin >> n >> k;
if (k == 1) {
std::cout << 1 << std::endl;
return 0;
}
fac[0] = ifac[0] = 1;
for (int i = 1; i <= n * k; i++) fac[i] = fac[i - 1] * i % mod;
ifac[n * k] = power(fac[n * k], mod - 2);
for (int i = n * k - 1; i >= 1; i--)
ifac[i] = ifac[i + 1] * (i + 1) % mod;
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= i; j++) {
f[i][j] = f[i - 1][j];
if (j == 0) continue;
int cc = C(n * k - i - (j - 1) * (k - 1) - 1, k - 2);
f[i][j] = (f[i][j] + f[i][j - 1] * (n - j + 1) % mod * cc) % mod;
}
}
std::cout << f[n][n] << std::endl;
return 0;
}
inline int C(int n, int m) {
return n < m ? 0 : fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
inline int power(int a, int n) {
int ret = 1;
while (n) {
if (n & 1) ret = ret * a % mod;
a = a * a % mod; n /= 2;
} return ret;
}