%3 题解(组合计数+隔板法)

Description

给定 n , m n,m n,m,求满足以下条件的整数序列 A = ( a 1 , a 2 , … , a N ) A = (a_1, a_2, \ldots, a_N) A=(a1,a2,,aN) 的个数模 998244353 998244353 998244353 的值。

条件:

  • ∀ i ∈ [ 1 , n ) :   a i ≢ a i + 1 ( m o d 3 )   ∧   a i ≤ a i + 1 \forall i \in [1,n):\ a_i \not\equiv a_{i+1} \pmod 3\ \wedge\ a_i \le a_{i+1} i[1,n): aiai+1(mod3)  aiai+1

  • a i ∈ [ 0 , m ] a_i \in [0,m] ai[0,m]

Analysis

首先这道题有一个很套路的 dp 做法:设 f i , j f_{i,j} fi,j 表示第 i i i 个数,上一个选的数取模 3 3 3 的余数为 j j j 的方案数。这样转移复杂度是 O ( n m ) O(nm) O(nm) 的。

考虑用组合数学的计算方法优化成线性复杂度。

发现相邻两项不同余 3 3 3 其实就相当于相邻两项的差不整除 3 3 3

则我们可以用差分数组来改变一下题目的原有条件。

定义:
∀ i ∈ [ 2 , n ] : d i = a i − a i − 1 d 1 = a 1 \forall i \in [2,n]:d_i = a_i - a_{i-1} \\ d_1 = a_1 \\ i[2,n]:di=aiai1d1=a1
则题目要求 ∀ i ∈ [ 2 , n ] : d i ≢ 0 ( m o d 3 ) \forall i \in [2,n]: d_i \not\equiv 0 \pmod 3 i[2,n]:di0(mod3)。也就是在满足条件的 A A A 中, d i d_i di 可以被表示为 3 k + 1 3k+1 3k+1 3 k + 2 3k+2 3k+2 的形式。

看到这里脑中就应该浮现出隔板法的模型了。题目就又可以转化为:
假设  ∃ a n + 1 = m ⇒ ∑ i = 1 n + 1 d i = m ⇒ 求不定方程  x 1 + x 2 + ⋯ + x n + 1 = m   ( x i ∈ N )  的解的组数 假设\ \exist a_{n+1}=m \\ \Rightarrow \sum\limits_{i=1}^{n+1} d_i=m \\ \Rightarrow 求不定方程\ x_1 + x_2 + \cdots + x_{n+1} = m\ (x_i \in \mathbb{N})\ 的解的组数 假设 an+1=mi=1n+1di=m求不定方程 x1+x2++xn+1=m (xiN) 的解的组数
根据隔板法,若不考虑 d i d_i di 的限制,我们可能会立刻想到答案为 ( m + 1 n ) \dbinom{m+1}{n} (nm+1)

解释也很简单:在我们要把数字 m m m 分成 n + 1 n+1 n+1 份,就相当于在值域 [ 0 , m ] [0,m] [0,m] m + 1 m+1 m+1 个数里插 n n n 个隔板。

但这是不对的。

原因是这道题和常规的隔板不大一样,常规隔板是 n n n 个元素往 m m m 个空里放,两个元素不能放到同一个空里。这样答案是 ( m n ) \dbinom{m}{n} (nm),但这道题两个物品可以放在同一个空里面(因为 x x x 可以为 0 0 0)。

我们考虑如何转化,有一个很妙的思路:考虑将等式左边的所有 x x x 都加上 1 1 1,这样就能保证所有的 x ≥ 1 x \ge 1 x1。此时原方程变为: x 1 + x 2 + ⋯ + x n + 1 = m + n + 1 x_1 + x_2 + \cdots + x_{n+1} = m+n+1 x1+x2++xn+1=m+n+1,此时再用正常的隔板法就能求得正确的答案了: ( m + n n ) \dbinom{m+n}{n} (nm+n)。(上面不是 m + n + 1 m+n+1 m+n+1 是因为此时 x x x 一定不为 0 0 0,所以答案的值域实际为 [ 1 , m + n ] [1,m+n] [1,m+n]

还有一种更巧妙的解释方式。考虑把隔板也看成是一个物品,那么我们此时就有 m + n m+n m+n 个物品,我们只要选出 n n n 个隔板所在的位置,就能确定剩下物品的位置。所以选定隔板的方案数就是总的方案数: ( m + n n ) \dbinom{m+n}{n} (nm+n),答案一样。但第一个方法是一个通法,适用范围更广。

然后考虑加上不同余 3 3 3 的限制。承接上文,所有的 d i ( 1 < i < n + 1 ) d_i(1 < i < n+1) di(1<i<n+1) 都可以表示为 3 k + 1 3k+1 3k+1 3 k + 2 3k+2 3k+2 的形式。

发现隔板法其实可以转化为一个“分数字”的过程。

所以我们可以枚举有 x x x d i d_i di 3 k + 1 3k+1 3k+1,那么就有 n + 1 − x n+1-x n+1x 3 k + 2 3k+2 3k+2(因为一共 n + 1 n+1 n+1 个数)。

因为所有的 d i d_i di 都有 3 k 3k 3k 这个部分,所以我们先放多出来的 1 1 1 2 2 2。这样,摆在我们面前的问题就是:所有的 d i d_i di 初始都是 0 0 0,我们要把 x x x 1 1 1 n + 1 − x n+1-x n+1x 2 2 2 分给所有的 d i d_i di。要注意的是分完之后只有 d 1 d_1 d1 d n + 1 d_{n+1} dn+1 可以是 0 0 0,剩下的必须是 1 1 1 2 2 2

那么,单独放 1 1 1 2 2 2 的方案数显然就是 ( n + 1 x ) \dbinom{n+1}{x} (xn+1)。(确定了放 1 1 1 的个数后 2 2 2 的直接就放上去就可以)

因为现在我们不确定 d 1 d_1 d1 d n + 1 d_{n+1} dn+1,所以我们可以先枚举 d 1 ∈ [ 0 , 2 ] d_1 \in [0,2] d1[0,2]。( d n + 1 d_{n+1} dn+1 先不急,后面有妙用)

分完之后,我们要分的数还剩下 m − d 1 − x − 2 ( n + 1 − x ) m - d_1 - x - 2(n+1-x) md1x2(n+1x)(如果剩下的数小于 0 0 0 无解),这些数要变成若干个 3 k 3k 3k 分给 n + 1 n+1 n+1 个数,其实就是变成若干个 3 3 3 分给每个数。(因为我们不关注具体的分数过程,只关心方案数)

我们希望 m − d 1 − x − 2 ( n + 1 − x ) m - d_1 - x - 2(n+1-x) md1x2(n+1x) 一定是 3 3 3 的倍数,这样是符合我们上面的逻辑的。但一切事情都不会进行的那么顺利,假设它不是 3 3 3 的倍数,因为我们还没分 d n + 1 d_{n+1} dn+1,我们就可以利用处于“法外之地”的 d n + 1 d_{n+1} dn+1 使它变成 3 3 3 的倍数。即 d n + 1 d_{n+1} dn+1 的值一定是 [ m − d 1 − x − 2 ( n + 1 − x ) ]   m o d   3 [m-d_1-x-2(n+1-x)] \bmod 3 [md1x2(n+1x)]mod3

现在,解决了余数的问题,我们要分的 3 3 3 的个数就是 p = ⌊ m − d 1 − x − 2 ( n + 1 − x ) 3 ⌋ p = \lfloor\dfrac{m-d_1-x-2(n+1-x)}{3}\rfloor p=3md1x2(n+1x) 了。

把这么多个 3 3 3 分给 n n n 个数,根据上面讲的隔板法,方案数即为: ( p + n n ) \dbinom{p+n}{n} (np+n)

最后的最后,把分 1 , 2 1,2 1,2 的方案数和分 3 3 3 的方案数乘起来即为答案: ( n + 1 x ) ( p + n n ) \dbinom{n+1}{x}\dbinom{p+n}{n} (xn+1)(np+n)

这道题相当于 NOIP T2 的难度,是一道数学推导的好题!关键在于转换差分数组和想到”分数字“这一重要操作。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e7 + 2e6; // C(n+p,n) 中n+p可能大于1e7,要开得大一点
const int mod = 998244353;
typedef long long ll;

int fac[maxn], ifac[maxn]; // 阶乘、阶乘逆元(开int数组减小空间)

ll Pow(ll x, ll y){ // 快速幂
	if(y == 0) return 1;
	if(y == 1) return x % mod;
	ll sq = Pow(x, y / 2);
	if(y & 1){
		return sq * sq % mod * x % mod;
	}
	return sq * sq % mod;
}

int C(int n, int m){ // C(n,m): n个数里选m个的组合数
	// C(n,m) = n! / [m! * (n-m)!]
	return (n < m || m < 0)? 0 : (1LL * fac[n] * ifac[m] % mod * ifac[n - m] % mod);
}

void pretreatment(){
	// 求普通阶乘
	fac[0] = 1;
	for(int i = 1; i < maxn; i ++) fac[i] = 1LL * fac[i - 1] * i % mod;
	
	// 递推求阶乘逆元
	ifac[maxn - 1] = Pow(fac[maxn - 1], mod - 2);
	for(int i = maxn - 2; i >= 0; i --) ifac[i] = 1LL * ifac[i + 1] * (i + 1) % mod;
}

signed main()
{
	pretreatment();
	int n, m; cin >> n >> m;
	ll ans = 0;
	for(int x = 0; x <= n - 1; x ++){
		for(int d1 : {0, 1, 2}){
			int p = (m - d1 - x - 2*(n - 1 - x));
			if(p < 0) continue;
			p /= 3; // 细节:先除3有可能让一个负数变成0(例如-2/3=0,所以要先判断再除)
			ans += 1LL * C(n - 1, x) * C(p + n, n) % mod;
			ans %= mod;
		}
	}
	cout << ans << '\n';
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值