【组合数学】

【组合数学】

一般是推公式
image

模版代码

#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

思路

image
->公式
image

代码

#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

题目大意

d2419e28-4acb-4884-b96d-4989f6b6846b

思路

设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;
}
posted @ 2025-02-06 11:41  White_ink  阅读(26)  评论(0)    收藏  举报