「JSOI2013」游戏中的学问

「JSOI2013」游戏中的学问

传送门

考虑 \(\text{DP}\)

\(dp_{i, j}\) 表示将前 \(i\) 个人分成 \(j\) 个集合,并且第 \(i\) 个人在第 \(j\) 个集合的方案数。

转移就是:

\[dp_{i, j} = dp_{i - 1, j} \times (i - 1) + dp_{i - 3, j - 1} \times {i - 1 \choose 2} \times 2 \]

其中前面那一项就是加入一个人,感觉有点像第一类斯特林数递推式中的一部分。。。

然后后面那一项就是我们新开一个集合,强制 \(i\) 在这个集合中,然后再随便选两个人来组成一个圈,因为 \(3\) 个人的圆排列个数是 \(2\) 所以还要乘个 \(2\)

#include <cstdio>
#define rg register
#define file(x) freopen(x".in", "r", stdin), freopen(x".out", "w", stdout)
template < class T > inline void read(T& s) {
    s = 0; int f = 0; char c = getchar();
    while ('0' > c || c > '9') f |= c == '-', c = getchar();
    while ('0' <= c && c <= '9') s = s * 10 + c - 48, c = getchar();
    s = f ? -s : s;
}
 
const int _ = 3005;
 
int n, k, p, dp[_][_ / 3 + 5];
 
int main() {
#ifndef ONLINE_JUDGE
    file("cpp");
#endif
    read(n), read(k), read(p);
    dp[0][0] = 1;
    for (rg int i = 3; i <= n; ++i)
    	for (rg int j = 1; j <= i && j <= k; ++j) {
	        dp[i][j] = (dp[i][j] + 1ll * dp[i - 1][j] * (i - 1) % p) % p;
	        dp[i][j] = (dp[i][j] + 1ll * dp[i - 3][j - 1] * (i - 1) % p * (i - 2) % p) % p;
    	}
    printf("%d\n", dp[n][k]);
    return 0;
}
posted @ 2020-02-08 15:34  Sangber  阅读(142)  评论(0编辑  收藏  举报