牛客网暑期ACM多校训练营(第六场)C.Generation I 组合数+逆元+思维

链接:https://www.nowcoder.com/acm/contest/144/C
来源:牛客网
 

时间限制:C/C++ 3秒,其他语言6秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

Oak is given N empty and non-repeatable sets which are numbered from 1 to N.

Now Oak is going to do N operations. In the i-th operation, he will insert an integer x between 1 and M to every set indexed between i and N.

Oak wonders how many different results he can make after the N operations. Two results are different if and only if there exists a set in one result different from the set with the same index in another result.

Please help Oak calculate the answer. As the answer can be extremely large, output it modulo 998244353.

输入描述:

The input starts with one line containing exactly one integer T which is the number of test cases. (1 ≤ T ≤ 20)

Each test case contains one line with two integers N and M indicating the number of sets and the range of integers. (1 ≤ N ≤ 1018, 1 ≤ M ≤ 1018, )

输出描述:

For each test case, output "Case #x: y" in one line (without quotes), where x is the test case number (starting from 1) and y is the number of different results modulo 998244353.

 

示例1

输入

2
2 2
3 4

输出

Case #1: 4
Case #2: 52

题目大意:

现在有N个空的集合(集合是普通定义上的集合),现在有N次操作,每一次操作可以把在[1,M]中选一个数X放入到[i,N]的集合中(注意是[i,N]全部集合),现在问N次操作之后这些集合组成的集合不同的的个数有多少个。

题目分析:

其实这题有一点卡读题,,

从题目中我们可以看出一些信息,对于第i个集合而言,前i-1个集合都是它的子集,也就是说前i-1个集合中存在的数X在第i个集合都存在。

那么显然如果我们对于最后一个集合(即第N个集合),如果我们假设最后它有K个数,那么如果忽略N的影响时,我们可以在M个数中取K个然后全排列即A(M,K)种方案。现在我们把N也纳入考虑范围,这样就相当于把K个数插入到N个空位。但是有一点要注意的是第一个空位一定要放入一个数,由于刚才在算全排列的时候已经相当于把第一个数选出来的了,因此选取选取位置的方案是C(N-1,K-1)。

综上对于最后又K个数的情况,有A(M,K)*C(N-1,K-1)的情况。

而结果就变成\sum_{K=1}^{min(N,M)}A(M,K)*C(N-1,K-1)

看到这里其实基本就出结果了,因为求和上界的原因这个求和式最多也就1e6项。

不过这里还是有一些细节部分。

首先要对于上述公式我们显然是不能直接打出阶乘的逆元表来加速的(因为nm的范围问题)

但是我们观察式子可以看出,他们都是关于N或M的算式

对于A,如果我们要使A(M,i)->A(M,i+1)只需要A(M,i)*(M-i)

对于C,如果我们要使C(N-1,j)->C(N-1,j+1)只需要C(N-1,j)*(N-j-1)/(j+1)

以上可以通过公式证明

那么显然我们可以在求和的过程中不断计算出下一项的A和C就行了,这里需要一个1e6内的逆元表。

逆元表的做法做种多样,不过因为这里多组测试样例并且基本需要从1~1e6的数的逆元,因此使用线性打表比较好(时间复杂度O(N))

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<map>
#include<algorithm>
#include<queue>
#include<set>
#include<cstdio>
#include<functional>
#include<iomanip>
#include<cmath>
#include<stack>
#include<iomanip>
#include<functional>
#include<iomanip>
#include<bitset>
#define lson l,m
#define rson m+1,r
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int maxn = int(1e6) + 100;
const int BN = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const LL mod = 998244353;
LL inv[maxn] = { 1,1 };
//O(N)打印逆元表
void init() {
	for (LL i = 2; i<maxn; i++) {
		inv[i] = (mod - mod / i)*inv[mod%i] % mod;
	}
}
int main() {
	//ios::sync_with_stdio(false);
	//cin.tie(0);
	//freopen("D:\\cpp\\test.txt", "r", stdin);
	//freopen("D:\\cpp\\\comper\\8.in", "w", stdout);
	init();
	int T, kase = 1;
	scanf("%d", &T);
	while (T--) {
		LL n, m;
		scanf("%lld%lld", &n, &m);
		LL ans = 0;
		LL A = m % mod, C = 1;//第一项
		for (LL i = 1, j = 0; i <= m && j <= n-1; i++,j++) {
			ans = (ans + A * C%mod) % mod;
			A = (m - i) % mod*A%mod;//(m - i)和A的顺序不能反过来,因为m-i是没取模状态的
			C = (n - j - 1) % mod*C%mod*inv[j + 1] % mod;
		}
		printf("Case #%d: %lld\n", kase++, ans);
	}
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值