突破口:dp的阶段(按什么分步)

这里显然不好存每个颜色还剩余个数的这种暴力填每一位所需的状态,考虑按颜色填。
发现题目的性质,并证明其必要性,就是题目的等价条件了。
最后的合法串的判定等价于任何前缀的白色个数大于等于其它颜色种类数。
条件是:“任意+不等式”,即所有前缀 白色个数-其它颜色种类数 的最小值非负。
最小值显然在使前缀出现新颜色即每种其它颜色第一次出现处可能取到。
所以dp,设\(dp(i,j)\)表示填了\(i\)个白色,\(j\)个其它颜色,有效状态满足\(i\le j\)。转移每次填最靠前的空位对应颜色。

点击查看代码
#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2005;
const int M = N * N;
ll jc[M], ijc[M], dp[N][N]; 
ll ksm(ll a, ll b) {ll res(1); for(; b; b >>= 1, a = a * a % mod)if(b & 1)res = res * a % mod; return res;}
ll binom(int x, int y) {return jc[x] * ijc[y] % mod * ijc[x - y] % mod;}
void init(int up) {
	jc[0] = 1; for(int i = 1; i <= up; i++) jc[i] = jc[i - 1] * i % mod;
	ijc[up] = ksm(jc[up], mod - 2); for(int i = up; i; i--) {ijc[i - 1] = ijc[i] * i % mod;}
}
int main() {
	int n, k, nk;
	scanf("%d%d", &n, &k);
	if(k == 1) {puts("1"); return 0;}
	init(nk = n * k);
	dp[0][0] = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j <= i; j++) {
			dp[i][j] = dp[i - 1][j];
			if(j) {dp[i][j] = (dp[i][j] + dp[i][j - 1] * (n - j + 1) % mod * binom(nk - i - (j - 1) * (k - 1) - 1, k - 2)) % mod;}
		}
	}
	printf("%lld", dp[n][n]);
	return 0;
}