【组合数学】
【组合数学】
一般是推公式

模版代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const int MOD = 1e9 + 7; // 定义模数
const int MAX = 1e6 + 10;
ll fact[MAX]; // 存储阶乘
ll inv_fact[MAX]; // 存储阶乘的逆元
// 快速幂
ll pow_mod(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
//预处理阶乘和逆元
void precompute() {
fact[0] = 1;
for (int i = 1; i < MAX; ++i) {
fact[i] = fact[i - 1] * i % MOD; // 计算阶乘
}
inv_fact[MAX - 1] = pow_mod(fact[MAX - 1], MOD - 2); // 计算最大阶乘的逆元
for (int i = MAX - 2; i >= 0; --i) {
inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD; // 递推计算逆元
}
}
//使用时记得反过来!
// 计算组合数 C(n, k)
ll C(int n, int k) {
if (n < 0 || k < 0 || n < k) return 0; // 边界条件
return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD;
}
// 计算排列数 A(n, k)
ll A(int n, int k) {
if (n < 0 || k < 0 || n < k) return 0; // 边界条件
return fact[n] * inv_fact[n - k] % MOD;
}
注意点
(1)记得C和A函数里的对应式子是反过来的!(前大后小)
(2)记得main里要先处理阶乘和逆元!
组合数表
当n<=1e4的时候用
利用C[i][j]=(C[i-1][j]+C[i-1][j-1])性质
ll C[N][N],f[N];
void init(){
f[0]=1LL;
for(int i=1;i<=6e3;i++){
if(i==1) f[i]=1LL;
f[i]=f[i-1]*(ll)i%mod_phi;
}
for(int i=0;i<=6e3;i++){
C[i][0]=1LL;
C[i][i]=1LL;
for(int j=1;j<i;j++){
C[i][j]=add(C[i-1][j-1],C[i-1][j],mod_phi);
}
}
}
题目整理
小红的好排列
https://ac.nowcoder.com/acm/contest/100902/E
思路

->公式

代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const int MOD = 1e9 + 7; // 定义模数
const int MAX = 1e6 + 10;
ll fact[MAX]; // 存储阶乘
ll inv_fact[MAX]; // 存储阶乘的逆元
// 快速幂
ll pow_mod(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
//预处理阶乘和逆元
void precompute() {
fact[0] = 1;
for (int i = 1; i < MAX; ++i) {
fact[i] = fact[i - 1] * i % MOD; // 计算阶乘
}
inv_fact[MAX - 1] = pow_mod(fact[MAX - 1], MOD - 2); // 计算最大阶乘的逆元
for (int i = MAX - 2; i >= 0; --i) {
inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD; // 递推计算逆元
}
}
// 计算组合数 C(n, k)
ll C(ll n, ll k) {
if (n < 0 || k < 0 || n < k) return 0; // 边界条件
return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD;
}
// 计算排列数 A(n, k)
ll A(ll n, ll k) {
if (n < 0 || k < 0 || n < k) return 0; // 边界条件
return fact[n] * inv_fact[n - k] % MOD;
}
ll n;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
//记得预处理阶乘和逆元!
precompute();
ll cnt1=n/2;
ll cnt2=n/3;
//这里注意要一个一个乘过去取模->会爆ll
ll ans=C(cnt2,cnt1-cnt2);
ans=ans*C(n-cnt2,cnt1-cnt2)%MOD;
ans=ans*A(cnt2,cnt2)%MOD;
ans=ans*A(n-cnt2,n-cnt2)%MOD;
cout<<ans;
return 0;
}
小红开灯(三,easy)
https://ac.nowcoder.com/acm/contest/107000/C
一共(n-k+1)个区间->每个区间都有选or不选2种状态
->乘法原理 2^(n-k+1)
const ll mod=1e9+7;
ll n,k;
ll qmi(ll a,ll k,ll p){
ll res=1;
while(k>0){
if(k&1) res=res*a%p;
k>>=1;
a=a*a%p;
}
return res;
}
void solve(){
cin>>n>>k;
ll ans=qmi(2LL,(n-k+1),mod);
cout<<ans;
}
Christmas Tree Decoration
https://codeforces.com/contest/2182/problem/D
题目大意

思路
设a总和为q
(1)拿的顺序是循环的:有部分前面的要拿q/n+1次,其余也需要拿q/n次->统计个数做组合数即可
(2)因为可以拿0或ai,且拿的次数是平均数,因此只需要考虑上限
具体见代码
代码
void solve(){
cin>>n;
vector<i64> a(n+1,0);
i64 sum=0;
for(int i=0;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
i64 x=sum/n;
i64 r=sum%n;//最多只能有这些人拿q/n+1次
int cnt=0;//统计q+1的个数
bool is_ok=true;//若超过q+1次则无方案
for(int i=1;i<=n;i++){
if(a[i]>x+1){
is_ok=false;
break;
}
if(a[i]==x+1) cnt++;
}
if(!is_ok || cnt>r){
cout<<0<<endl;
return;
}
//从r人中选出cnt个人排列(A),剩下的自由排列(n-cnt阶乘)
i64 ans=fact[n-cnt]*A(r,cnt)%mod;
cout<<ans<<endl;
}
【二进制取位结合】Unfair Game
https://codeforces.com/contest/2184/problem/D
题目大意
对于一个数有两个操作:遇到奇数\(-1\) 遇到偶数\(÷2\)
现要将该数操作到0为止,求\(1-n\)中操作步数\(>=k\)的数字个数
思路
从最低位开始 遇到0直接÷ 遇到1要先-后÷
所有的位数都要被÷一遍 -> 最高位位数m
遇到除了最高位的1要-一遍 -> 1的个数c
那么要求\(m+c>k\)
那么枚举最高位和1的个数,如果满足条件则通过组合数放1
AC代码
注意杨辉三角公式\(C(i,j)=C(i-1,j-1)+C(i-1,j)\)的运用
const int N=45;
i64 n,k;
i64 C_[N][N];
void init(){//初始化组合数
for(i64 i=0;i<=35;i++){
for(i64 j=0;j<=35;j++){
if(i<j) C_[i][j]=0;
else if(j==0) C_[i][j]=1;
//杨辉三角公式:C(i,j)=C(i-1,j-1)+C(i-1,j)
else C_[i][j]=C_[i-1][j]+C_[i-1][j-1];
}
}
}
i64 wei(i64 n){
for(i64 i=63;i>=0;i--){
if((n>>i)&1LL) return i+1;
}
return 1;
}
void solve(){
cin>>n>>k;
i64 w=wei(n);
//cout<<w<<endl;
i64 ans=0;
for(int m=0;m<=w-2;m++){//枚举最高位
for(int c=1;c<=m+1;c++){//枚举总1的个数:最高位肯定是1 注意这里m位数从0开始所以个数要+1
if(m+c<=k) continue;
ans+=C_[m][c-1];//m位除去首位 剩下的位数放c个1
}
}
//特判n
if(w>k) ans++;
cout<<ans<<endl;
}

浙公网安备 33010602011771号