P8106[Cnoi2021]数学练习-题解

刚刚学完排列组合特来写一篇题解

1. 思路

首先题目要求我们将 \(1\)~\(n\) 的数分为两个子集。
\(|S| \notin S\)\(|T| \notin T\)
\(|S|\) 表示的是 \(S\) 集合中元素的个数。
由题目的特殊条件我们可以得到该条件的一个充要条件
\(|S| \in T\)\(|T| \in S\)

这样我们在 “ 安置 ” 好 \(|S|\)\(|T|\) 后就可以用组合数公式轻松计算出答案了。
但是这边仍然有一个性质我们没有用到 :
\(n\) 为偶数时 \(|S| \neq |T|\) 这是显然的,也是必要的。

2. 解法

枚举 $1 \(~\) n - 1 $ 作为 S 集合的元素个数。
已有 $ |T| \in S$。
利用组合数公式挑选剩下 \(i - 1\) 个数。
由于 \(n\) 个数全部挑完 , 所以剩下的数全部纳入 \(T\) 集合。
我们只需要算 \(S\) 集合满足题意的个数即可。
附上组合数公式 :

注意到除号且在模意义下 , 所以我们需要求 逆元
注意到此题 \(mod\) 的特殊性 , 所以我们可以使用 费马小定理 求解逆元。
不知道逆元建议先做做 这道题
最后注意 特判 \(n\) 为偶数 的情况就可以了。

3. Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
typedef long long ll;
ll mod=998244353;
ll n;
ll frac[maxn];
ll x,y;
ll inv(ll a,ll s,ll p){ // 费马小定理求逆元
	ll ans=1; 
	while(s){
		if(s&1) ans=ans*a%p;
		a=a*a%p;
		s>>=1;
	}
	return ans%p;
}
ll C(ll m,ll n){
	ll x=inv(frac[m]*frac[n-m]%mod,mod-2,mod);
    return frac[n]*x%mod;
}
int main(){
    scanf("%lld",&n);
    ll ans=0;
    frac[0]=1;
    for(ll i=1;i<=n;i++) frac[i]=frac[i-1]*i%mod; // 预处理阶乘
    ll mid=n/2;
    bool judge=false;
    if(n%2==0) judge=true;
   	for(ll i=1;i<=n-1;i++){
   		if(judge==true&&i==mid) continue; // 特判 n 为偶数
   		ans=(ans+C(i-1,n-2))%mod;
	} 
   	printf("%lld",ans);
    return 0;
}
posted @ 2022-06-06 17:01  ThinkGone  阅读(47)  评论(0编辑  收藏  举报