2025.02.27 CW 模拟赛 A. 麻将

A. 麻将

怎么回事呢? 还是太浮躁了吗?

题面

给定 $ n $, $ m $ 和长度为 $ n $ 的序列 $ a $, 保证 $ n $ 为 3 的倍数, 且 $ a_{i} \in [1, m] $.

一个可重三元集合被称为面子, 当且仅当其为形如 $ \{x, x, x\} $ 或 $ \{x, x+1, x+2\} $ 的集合.

试将这 $ n $ 个元素划分为 $ \frac{n}{3} $ 个面子, 输出方案数对 100000007 取模后的结果.

两种划分方案不同, 当且仅当存在一种面子, 在两个划分方案中出现次数不同.

思路

考虑一个 DP: \(f_{i, j, k}\) 表示到第 \(i\) 个数还剩下 \(j\) 个且第 \(i - 1\) 个数剩下 \(k\) 个的方案数. 答案即为 \(f_{m, 0, 0}\).

怎么转移呢? 可以发现, 如果第 \(i - 2\) 个不被用完, 那么这个方案就一定不合法了, 于是我们考虑枚举第 \(i\) 个数合成了 \(l\) 个形如 \(\{ x, x, x \}\) 的面子, 就有

\[f_{i, j, k} = \sum_{l = 0}^{\lfloor \frac{cnt_i - j}{3} \rfloor} f_{i - 1, k + cnt_i - j - 3 \times l, cnt_i - j - 3 \times l} \]

其中 \(cnt_i\) 指的是 \(i\) 这个数出现了多少次. 同时在转移的时候还需要满足当前状态合法. \((\)\(k + cnt_i - j - 3\) 不能大于 \(\max(cnt_{i - 1}, cnt_{i - 2})\), 否则直接 break 掉即可\()\)

在实现时需要使用滚动数组, 清空时要注意范围.

#include "cstdio"
#include "cstring"

using namespace std;

constexpr int N = 2e3 + 1, mod = 1e9 + 7;

int n, m, t[N], f[2][N][N];

void addon(int &x, int y) { if ((x += y) >= mod) x -= mod; }

int max(int x, int y) { return x <= y ? y : x; }

void init() {
	scanf("%d %d", &n, &m);
	for (int i = 1, x; i <= n; ++i)
		scanf("%d", &x), ++t[x];
}

void calculate() {
	f[0][0][0] = 1;
	for (int i = 1; i <= m; ++i) {
		for (int j = 0, _ = max(t[i], i > 1 ? t[i - 2] : 0); j <= _; ++j)
			for (int k = 0, __ = max(t[i - 1], i > 2 ? t[i - 3] : 0); k <= __; ++k) f[i & 1][j][k] = 0;
		for (int j = 0; j <= t[i]; ++j)
			for (int k = 0; k <= t[i - 1]; ++k) {
				for (int l = (t[i] - j) / 3; ~l; --l) {
					if (k + t[i] - j - 3 * l > (i > 1 ? max(t[i - 1], t[i - 2]) : t[i - 1])) break;
					addon(f[i & 1][j][k], f[!(i & 1)][k + t[i] - j - 3 * l][t[i] - j - 3 * l]);
				}
			}
	}
	printf("%d", f[m & 1][0][0]);
}

void solve() {
	init();
	calculate();
}

int main() {
	solve();
	return 0;
}
posted @ 2025-02-27 15:41  Steven1013  阅读(28)  评论(0)    收藏  举报