2020年牛客多校第五场C题-easy(纯组合计数不要生成函数的做法)

description

T T T组测试数据

对于两个长度为 K K K的数列 { a } \{a\} {a} { b } \{b\} {b},满足 ∑ i = 1 K a i = N , ∑ i = 1 K b i = M \sum_{i=1}^Ka_i=N,\sum_{i=1}^Kb_i=M i=1Kai=N,i=1Kbi=M

对于这两个数列,定义权值为 P = ∏ i = 1 K min ⁡ ( a i , b i ) P=\prod_{i=1}^K\min(a_i,b_i) P=i=1Kmin(ai,bi)

求所有可能的数列的权值之和 ∑ { a } , { b } P \sum_{\{a\},\{b\}}P {a},{b}P

答案对 998244353 998244353 998244353取模

K ≤ min ⁡ ( N , M ) N , M ≤ 5 e 5 T ≤ 100 K\le \min(N,M)\quad N,M\le 5e5\quad T\le 100 Kmin(N,M)N,M5e5T100

solution

虽然代码非常短,但是要理解简直要命

我们可以构造一个二维数组 c [ 2 ] [ K ] c[2][K] c[2][K],要求 c [ 0 ] [ i ] = c [ 1 ] [ i ] c[0][i]=c[1][i] c[0][i]=c[1][i]第i位不能为0

设计的初衷是让 c [ 0 ] [ K ] c[0][K] c[0][K]为构造 { a } \{a\} {a} c [ 1 ] [ K ] c[1][K] c[1][K]为构造 { b } \{b\} {b}服务

再构造两个一维数组 A [ K ] , B [ K ] A[K],B[K] A[K],B[K]第i位可以为0

通过构造出来的这些数组生成最后的 { a } , { b } \{a\},\{b\} {a},{b}

  • a [ i ] = c [ 0 ] [ i ] + A [ i ] a[i]=c[0][i]+A[i] a[i]=c[0][i]+A[i]
  • b [ i ] = c [ 1 ] [ i ] + B [ i ] b[i]=c[1][i]+B[i] b[i]=c[1][i]+B[i]

定义 ∑ i = 1 K c [ 0 ] [ i ] = ∑ i = 1 K c [ 1 ] [ i ] = S \sum_{i=1}^Kc[0][i]=\sum_{i=1}^Kc[1][i]=S i=1Kc[0][i]=i=1Kc[1][i]=S

则对于 { a } \{a\} {a}序列而言,剩下的 N − S N-S NS就需要由 A A A数组来弥补; { b } \{b\} {b}同理

考虑有多少个 A / B A/B A/B数组满足其求和恰好弥补了空缺 N − S / M − S N-S/M-S NS/MS

显然这可以通过组合数算出

  • A : C ( N − S + K − 1 , K − 1 ) A:C(N-S+K-1,K-1) A:C(NS+K1,K1)
  • B : C ( M − S + K − 1 , K − 1 ) B:C(M-S+K-1,K-1) B:C(MS+K1,K1)

A A A数组的计算方式为例, B B B同理

相当于把剩下的 N − S N-S NS分成恰好 K K K个盒子,隔板法,插入 K − 1 K-1 K1个板子

但是 A A A的条件可以为 0 0 0,意味着盒子可以为空,但这是隔板法不能求得的

可以通过给每个盒子分配 1 1 1的占位相当于一个盒子,等价于将限制变成至少为 1 1 1

考虑 C ( N − S + K − 1 , K − 1 ) ∗ C ( M − S + K − 1 , K − 1 ) C(N-S+K-1,K-1)*C(M-S+K-1,K-1) C(NS+K1,K1)C(MS+K1,K1)

发现这两个组合数相乘有非常实际的意义

这求出的恰好是将所有满足 ∀ 1 ≤ i ≤ K   c [ 0 ] [ i ] ≤ a [ i ] \forall_{1\le i\le K}\ c[0][i]\le a[i] 1iK c[0][i]a[i] { a } \{a\} {a} ∀ 1 ≤ i ≤ K   c [ 0 ] [ i ] ≤ b [ i ] { b } \forall_{1\le i\le K}\ c[0][i]\le b[i]\{b\} 1iK c[0][i]b[i]{b}组合都计算了恰好一次


接着我们考虑对于一组特定 c c c数组和特定 A , B A,B A,B数组(也就是一组特定的 { a } , { b } \{a\},\{b\} {a},{b})的贡献

∏ i = 1 K min ⁡ ( a i , b i ) \prod_{i=1}^K\min(a_i,b_i) i=1Kmin(ai,bi) 这是题目定义的贡献

思考一下,这样一组特定的 { a } , { b } \{a\},\{b\} {a},{b},会被多少个 c c c计算到?

  • 显然是 ∏ i = 1 K min ⁡ ( a i , b i ) \prod_{i=1}^K\min(a_i,b_i) i=1Kmin(ai,bi)

    每一位 i i i都满足 c [ 0 ] [ i ] = c [ 1 ] [ i ] ≤ min ⁡ ( a i , b i ) c[0][i]=c[1][i]\le\min(a_i,b_i) c[0][i]=c[1][i]min(ai,bi)就行,因为 c c c要求不能为 0 0 0,而 A , B A,B A,B要求可以为 0 0 0

    将每一位 c [ i ] c[i] c[i]的可取值个数相乘,即是能利用 A , B A,B A,B修补成 { a } , { b } \{a\},\{b\} {a},{b}的所有 c c c的组合

于是乎,发现非常巧妙地将答案的乘积和转化成了计数个数贡献

再转个弯,我们直接计算每个特定的 c c c会对多少个 { a } , { b } \{a\},\{b\} {a},{b}提供 1 1 1的计数贡献,求和就等价于原问题的总贡献

然而,又发现了一个非常巧妙的性质,和(S)相同的 c c c产生的计数贡献是一样的(但不是贡献的 { a } , { b } \{a\},\{b\} {a},{b}完全相同的意思)

  • 要证明这个性质,就需要再转个弯,用到最开始的组合数推导

    剩下的 N − S , M − S N-S,M-S NS,MS都需要 A , B A,B A,B的补给

    A , B A,B A,B怎么补给,最后生成的 { a } , { b } \{a\},\{b\} {a},{b}会因此而固定

    是一对一的关系

这个计数贡献自然是 C S − 1 , K − 1 C_{S-1,K-1} CS1,K1

  • 插板法

    c c c的和为 S S S,要求分成 K K K个盒子,盒子不能为空

    相当于有 S − 1 S-1 S1个空位需要插入 K − 1 K-1 K1个板

最后就成功不用生成函数解决这道题了!!!

现在总结,就只有一句话,最开始的 c , A , B c,A,B c,A,B构造简直就是神来之笔

后面所有的推导全都是建立在最开始的规则设定基础上

真的妙极了!!

code

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 1000005
int fac[maxn], inv[maxn];

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void init( int n ) {
	fac[0] = inv[0] = 1;
	for( int i = 1;i <= n;i ++ )
		fac[i] = fac[i - 1] * i % mod;
	inv[n] = qkpow( fac[n], mod - 2 );
	for( int i = n - 1;i;i -- )
		inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}

int C( int n, int m ) {
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

signed main() {
	init( 1e6 );
	int n, m, k, T;
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld %lld %lld", &n, &m, &k );
		int ans = 0;
		for( int i = k;i <= min( n, m );i ++ )
			ans = ( ans + C( i - 1, k - 1 ) * C( n - i + k - 1, k - 1 ) % mod * C( m - i + k - 1, k - 1 ) ) % mod;
		printf( "%lld\n", ans );
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值