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;
}