P8106 [Cnoi2021] 数学练习解题报告

[Cnoi2021] 数学练习

题目背景

「Cnoi2021」Cirno’s Easy Round II 热身赛开始了。

题目描述

为了让选手们重视文化课,Cirno 特意加入了一道 Kamishirasawa Keine 老师的数学练习:

求将一个集合 U = { 1 , 2 , 3 , ⋯   , n } \texttt{U}=\{1,2,3,\cdots,n\} U={1,2,3,,n} 划分成两个子集 S , T S,T S,T,使得 ∣ S ∣ ∉ S , ∣ T ∣ ∉ T |S|\notin S,|T|\notin T S/S,T/T 的方案数。

由于选手都不会高精度,所以答案只需要对 998244353 998244353 998244353 取模即可。

输入格式

一行一个整数 n n n

输出格式

一行,一个整数,表示答案。

样例 #1

样例输入 #1

3

样例输出 #1

2

样例 #2

样例输入 #2

6

样例输出 #2

10

样例 #3

样例输入 #3

65535

样例输出 #3

459810767

提示

样例解释

#1: 两种合法的划分方案为 { 1 , 3 } , { 2 } \{1,3\},\{2\} {1,3},{2} { 2 } , { 1 , 3 } \{2\},\{1,3\} {2},{1,3}

数据范围

对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105

题目大意

求将一个集合 U = { 1 , 2 , 3 , ⋯   , n } \texttt{U}=\{1,2,3,\cdots,n\} U={1,2,3,,n} 划分成两个子集 S , T S,T S,T,使得 ∣ S ∣ ∉ S , ∣ T ∣ ∉ T |S|\notin S,|T|\notin T S/S,T/T 的方案数。

题目分析

举例

先考虑 n = 3 n=3 n=3 的情况。

  • 若划分 ∣ S ∣ = 1 , ∣ T ∣ = 2 |S| = 1,|T| = 2 S=1,T=2,则 1 ∉ S , 2 ∉ T 1 \notin S,2 \notin T 1/S,2/T,相应地, 2 ∈ S , 1 ∈ T 2 \in S,1 \in T 2S,1T,考虑现在 S S S 有多少种合法方式( T T T 可以先忽略,因为此时合法即满足上述条件的 S S S 构造出的 T T T 一定是合法的)。因为已经有两个元素被选,只能构造出 S = { 2 } , T = { 1 , 3 } S=\{2\},T=\{1,3\} S={2},T={1,3} 一种划分方案。
  • 若划分 ∣ S ∣ = 2 , ∣ T ∣ = 1 |S| = 2,|T| = 1 S=2,T=1,使用同理的方式构造出 S = { 1 , 3 } , T = { 2 } S=\{1,3\},T=\{2\} S={1,3},T={2} 一种划分方式

于是 n = 3 n=3 n=3 对应的所有划分方案数为 2 2 2

推广

接下来用相同的方式考虑 n n n 为任意数的情况。

  • 若划分 ∣ S ∣ = 1 , ∣ T ∣ = n − 1 |S| = 1,|T| = n-1 S=1,T=n1 ,则 1 ∉ S , n − 1 ∉ T 1 \notin S,n -1 \notin T 1/S,n1/T,相应地, n − 1 ∈ S , 1 ∈ T n-1 \in S, 1 \in T n1S,1T。此时 S S S 的构造方案数等价于在 n − 2 n-2 n2 个数中选出 1 − 1 1 - 1 11 个数的方案数,即 C n − 2 0 C_{n-2}^{0} Cn20
  • 若划分 ∣ S ∣ = 2 , ∣ T ∣ = n − 2 |S| = 2,|T| = n-2 S=2,T=n2 ,则 2 ∉ S , n − 2 ∉ T 2 \notin S,n -2 \notin T 2/S,n2/T,相应地, n − 2 ∈ S , 2 ∈ T n-2 \in S, 2 \in T n2S,2T。此时 S S S 的构造方案数等价于在 n − 2 n-2 n2 个数中选出 2 − 1 2 - 1 21 个数的方案数,即 C n − 2 1 C_{n-2}^{1} Cn21
  • … \dots
  • 若划分 ∣ S ∣ = n − 1 , ∣ T ∣ = 1 |S| = n-1,|T| = 1 S=n1,T=1 ,则 n − 1 ∉ S , 1 ∉ T n-1 \notin S,1 \notin T n1/S,1/T,相应地, 1 ∈ S , n − 1 ∈ T 1 \in S, n-1 \in T 1S,n1T。此时 S S S 的构造方案数等价于在 n − 2 n-2 n2 个数中选出 n − 2 n-2 n2 个数的方案数,即 C n − 2 0 C_{n-2}^{0} Cn20

于是可以总结出规律:

∣ S ∣ = x , ∣ T ∣ = n − x |S| = x,|T| = n-x S=x,T=nx,此时合法的划分方案总数为 C n − 2 x − 1 C_{n-2}^{x-1} Cn2x1,特别地当 x = n − x x = n-x x=nx 时,不存在合法的划分方案,即合法的划分方案数为 0 0 0

实现

于是根据规律,枚举 ∣ S ∣ |S| S 运用规律计算出合法方案数并相加。求组合数则预处理一下利用逆元求即可,因为 m o d mod mod 是一个质数,可以用费马小定理求逆元。

时间复杂度:预处理 O ( n log ⁡ n ) O(n\log n) O(nlogn),枚举及统计答案 O ( n ) O(n) O(n),总复杂度 O ( n log ⁡ n + n ) O(n \log n +n) O(nlogn+n)

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#define int long long

using namespace std;

inline int read()
{
	int w = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		w = (w << 1) + (w << 3) + (ch ^ 48);
		ch = getchar();
	}
	return w * f;
}

inline void write(int x)
{
	if (x < 0)
	{
		putchar('-');
		x = -x;
	}
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

const int mod = 998244353;
const int maxn = 1e7 + 5;
int frac[maxn], inv[maxn];
int qpow(int a, int b, int p)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = a * res % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}
void init(int n)
{
	frac[0] = inv[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		frac[i] = frac[i - 1] * i % mod;
		inv[i] = qpow(frac[i], mod - 2, mod);
	}
}
int C(int a, int b)
{
	return (frac[a] * inv[b] % mod) * inv[a - b] % mod;
}
int n;
int ans = 0;

signed main()
{
#ifndef ONLINE_JUDGE
#define LOCAL
	//freopen("in.txt","r",stdin);
#endif
	n = read();
	init(n);
	for (int i = 1, a, b; i <= n; i++)
	{
		a = i, b = n - i;
		if (a == b) continue;
//		cout << a << " " << b << endl;
		ans += C(n - 2, a - 1);
		ans %= mod;
	}
	write(ans);
	puts("");
#ifdef LOCAL
	fprintf(stderr, "%f\n", 1.0 * clock() / CLOCKS_PER_SEC);
#endif
	return 0;
}

一个小优化

根据组合数的性质:

∑ i = 0 n C n i = 2 n \sum_{i=0}^{n} C_{n}^{i} = 2^n i=0nCni=2n

  • 如果 n n n 为奇数,答案就是 2 n − 2 2^{n-2} 2n2
  • 如果 n n n 为偶数,答案就是 2 n − 2 − C n − 2 n 2 − 1 2^{n-2}-C_{n-2}^{\frac{n}{2}-1} 2n2Cn22n1

注意 1 , 2 1,2 1,2 时特判即可。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#define int long long

using namespace std;

inline int read()
{
	int w = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		w = (w << 1) + (w << 3) + (ch ^ 48);
		ch = getchar();
	}
	return w * f;
}

inline void write(int x)
{
	if (x < 0)
	{
		putchar('-');
		x = -x;
	}
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

const int mod = 998244353;
const int maxn = 1e5 + 5;
int frac[maxn], inv[maxn];
int qpow(int a, int b, int p)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = a * res % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}
void init(int n)
{
	frac[0] = inv[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		frac[i] = frac[i - 1] * i % mod;
		inv[i] = qpow(frac[i], mod - 2, mod);
	}
}
int C(int a, int b)
{
	if (b == 0) return 1;
	return (frac[a] * inv[b] % mod) * inv[a - b] % mod;
}
int n;
int ans = 0;

signed main()
{
#ifndef ONLINE_JUDGE
#define LOCAL
	//freopen("in.txt","r",stdin);
#endif
	n = read();
	if (n == 2 || n == 1)
	{
		cout << 0 << endl;
		return 0;
	}
	init(n);
	ans = qpow(2, n - 2, mod);
	if (n % 2 == 1)cout << ans << endl;
	else
	{
		ans -= C(n - 2, n / 2 - 1);
		ans %= mod;
		cout << (ans + mod) % mod << endl;
	}
#ifdef LOCAL
	fprintf(stderr, "%f\n", 1.0 * clock() / CLOCKS_PER_SEC);
#endif
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值