[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 1≤n≤105。
题目大意
求将一个集合 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 2∈S,1∈T,考虑现在 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∣=n−1 ,则 1 ∉ S , n − 1 ∉ T 1 \notin S,n -1 \notin T 1∈/S,n−1∈/T,相应地, n − 1 ∈ S , 1 ∈ T n-1 \in S, 1 \in T n−1∈S,1∈T。此时 S S S 的构造方案数等价于在 n − 2 n-2 n−2 个数中选出 1 − 1 1 - 1 1−1 个数的方案数,即 C n − 2 0 C_{n-2}^{0} Cn−20。
- 若划分 ∣ S ∣ = 2 , ∣ T ∣ = n − 2 |S| = 2,|T| = n-2 ∣S∣=2,∣T∣=n−2 ,则 2 ∉ S , n − 2 ∉ T 2 \notin S,n -2 \notin T 2∈/S,n−2∈/T,相应地, n − 2 ∈ S , 2 ∈ T n-2 \in S, 2 \in T n−2∈S,2∈T。此时 S S S 的构造方案数等价于在 n − 2 n-2 n−2 个数中选出 2 − 1 2 - 1 2−1 个数的方案数,即 C n − 2 1 C_{n-2}^{1} Cn−21。
- … \dots …
- 若划分 ∣ S ∣ = n − 1 , ∣ T ∣ = 1 |S| = n-1,|T| = 1 ∣S∣=n−1,∣T∣=1 ,则 n − 1 ∉ S , 1 ∉ T n-1 \notin S,1 \notin T n−1∈/S,1∈/T,相应地, 1 ∈ S , n − 1 ∈ T 1 \in S, n-1 \in T 1∈S,n−1∈T。此时 S S S 的构造方案数等价于在 n − 2 n-2 n−2 个数中选出 n − 2 n-2 n−2 个数的方案数,即 C n − 2 0 C_{n-2}^{0} Cn−20。
于是可以总结出规律:
若 ∣ S ∣ = x , ∣ T ∣ = n − x |S| = x,|T| = n-x ∣S∣=x,∣T∣=n−x,此时合法的划分方案总数为 C n − 2 x − 1 C_{n-2}^{x-1} Cn−2x−1,特别地当 x = n − x x = n-x x=n−x 时,不存在合法的划分方案,即合法的划分方案数为 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} 2n−2。
- 如果 n n n 为偶数,答案就是 2 n − 2 − C n − 2 n 2 − 1 2^{n-2}-C_{n-2}^{\frac{n}{2}-1} 2n−2−Cn−22n−1。
注意 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;
}