P14686 [ICPC 2025 Yokohama R] Charity Raffle 喂饭式详解

这两天讲的计数给我写爽了。

P14686 [ICPC 2025 Yokohama R] Charity Raffle

这题是个人物,不简单。

考虑这样一个模型,\(f_{n, m, k}\) 表示 \(n\) 个非负整数数,和为 \(k\),最大值不超过 \(m\) 的总方案数,这个问题有一个拓展的版本来自 [OI-Wiki](容斥原理 - OI Wiki),所谓“不定方程非负整数解计数”:给出不定方程 \(\sum_{i = 1}^n x_i = k\) 和 \(n\) 个限制条件 \(x_i \le b_i\),其中 \(k, b_i \in \Bbb{N}\),求方程的非负整数解的个数,可以自行拓展阅读。在这道题就是 \(b_i = m\),先考虑如果没有 \(x_i \le b_i\) 的限制,那么相当于求 \(n\) 个非负整数和为 \(k\) 的方案数,经典地:将 \(k\) 视为 \(k\) 个 \(1\),即 \(k\) 个不区分编号的小球,放入 \(n\) 个区分编号的袋子。这个问题直接做不好做,可以认为是 \(k\) 个小球中间插 \(n - 1\) 个板,每两个板之间就是一个袋子所包含的小球,但是由于一个空隙中可以插入多个板子,仍然不好做,再次转化为:共 \(n + k - 1\) 个位置,选 \(n - 1\) 个变为板或者选 \(k\) 个变为小球,思路瞬间清晰,方案 \(|U| = C_{n + k - 1}^{k}\)。OK,那么现在把 \(f_{n, m, k}\) 转化为容斥模型:

  1. 全集 \(U\):不定方程 \(\sum_{i=1}^n x_i = k\) 的非负整数解

  2. 元素:变量 \(x_i\)

  3. 属性:\(x_i\) 的属性即 \(x_i\) 满足的条件,即 \(x_i \le b_i\) 的条件

目标:所有变量满足对应属性时集合的大小,即 \(|\bigcap_{i=1}^n S_i|\)。

求交集的套路展开:

\[|\bigcap_{i=1}^n S_i| = |U| - |\bigcup_{i=1}^n \overline{S_i}|\\ |\bigcup_{i=1}^n \overline{S_i}| = \sum_{|A| = 1}^n (-1)^{|A| - 1} \sum |\bigcap \overline{S_{A_i}}| \]

这个好做吗?好像也不大好做QwQ

首先能排除选哪些 \(x_i \ge m + 1\),\(C_{n}^{|A|}\),然后是怎么转化后面的问题,由于有 \(|A|\) 个 \(x_i \ge m + 1\),不妨直接假设在这个 \(x_i\) 个位置上面本身就有 \(m + 1\),问题竟然转化为了在 \(n\) 个数和为 \(k - (m + 1) \times |A|\) 的方案数,太巧妙了,直接算

\[f_{n, m, k} = |U| - \sum_{|A| = 1}^{\min(n, \lfloor \frac{k}{m + 1} \rfloor)} (-1)^{|A| - 1} \times C_{n}^{|A|} \times C_{n + k - (m + 1) \times |A| - 1}^{n - 1} \]

对于这道题剩下的部分还有点复杂,先搁着,但是打表发现答案为 \(f_{n, m, k} - f_{n, m - 1, k - 1}\)。

//# pragma GCC optimize("Ofast")
# include <bits/stdc++.h>
# define int LL
# define fr front
# define il inline
# define fir first
# define sec second
# define vec vector
# define it iterator
# define pb push_back
# define lb lower_bound
# define ub upper_bound
# define all(x) x.begin(), x.end()
# define mem(a, b) memset(a, b, sizeof(a))

# define lc (t[p].l)
# define rc (t[p].r)
# define ls(x) (x << 1)
# define rs(x) (x << 1 | 1)
# define lson ls(p), l, mid
# define rson rs(p), mid + 1, r

# define sqr(x) ((x) * (x))
# define bpc __builtin_popcount
# define lowbit(x) ((x) & (-(x)))
# define geti(x, i) (((x) >> (i)) & 1)
# define set1(x, i) ((x) | (1 << (i)))
# define set0(x, i) ((x) & (~(1 << (i))))

# define debug1(x) cerr << #x << " = " << x << " "
# define debug2(x) cerr << #x << " = " << x << "\n"
# define bug cerr << "--------------------------\n"

# define each1(i, x) for(auto (i) : (x))
# define each2(i, x) for(auto (&i) : (x))
# define rep(i, a, b) for(int i = (a); i <= (b); ++ i)
# define pre(i, a, b) for(int i = (a); i >= (b); -- i)
# define G(i, h, u, ne) for(int i = h[(u)]; i; i = ne[i])
# define reps(i, a, b, c) for(int i = (a); i <= (b); i += (c))
# define pres(i, a, b, c) for(int i = (a); i >= (b); i -= (c))
using namespace std;

using DB = double;
using LL = long long;
using LDB = long double;
using PII = pair<int, int>;
using ULL = unsigned long long;

const int N = 1e6 + 10, mod = 998244353;
const int INF1 = 0x3f3f3f3f, INF2 = INT_MAX;
const LL INF3 = (LL)1e18, INF4 = 0x3f3f3f3f3f3f3f3f, INF5 = LLONG_MAX;

int n, m, k;
LL fac[N << 1], ifac[N << 1];

LL qpow(LL a, LL b, LL p){
	LL res = 1;
	while(b){
		if(b & 1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	
	return res % p;
}

void init(int n){
	fac[0] = 1;
	rep(i, 1, n) fac[i] = fac[i - 1] * i % mod;
	ifac[n] = qpow(fac[n], mod - 2, mod);
	pre(i, n, 1) ifac[i - 1] = ifac[i] * i % mod;
}

LL C(LL n, LL m){
	if(n < 0 || m < 0 || n < m) return 0;
	return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}

LL f(int n, int m, int k){
	if(n < 0 || m < 0 || k < 0) return 0; 
	int U = C(n + k - 1, k), res = 0;
	rep(A, 1, min(n, k / (m + 1))){
		res += (A & 1 ? 1 : -1) * C(n, A) % mod * C(n + k - (m + 1) * A - 1, n - 1) % mod;
		res %= mod;
		res += mod;
		res %= mod;
	}
	
	return (U - res + mod) % mod;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> k >> m;
	init(N - 10 << 1);
	cout << (f(n, m, k) - f(n, m - 1, k - 1) + mod) % mod;
	
	return 0;
}
posted @ 2026-10-06 21:25  MrTourist  阅读(15)  评论(0)    收藏  举报