组合数学学习笔记(入门版)

排列组合

例1

NOIP2016 组合数问题

首先有排列组合公式 Cnm=Cn1m+Cn1m1,根据这个递推出 Cnm 是否可以被k整除,再用二维前缀和预处理出答案即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;        
const int mod = 1e9 + 7, N = 2e3 + 10;
int t, k;
int n, m;
int C[N][N];
int sum[N][N];  //前缀和

inline int add(int a, int b) {
    if(a + b >= k) return a + b - k;
    else return a + b;
}

int main() {
    scanf("%d%d", &t, &k);
    n = m = 2000;
    for(int i = 1; i <= n; i++) C[i][0] = 1;
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                if(i == 1 && j == 1) C[i][j] = 1;
                else C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
                // cout << i <<' '<<j<<' '<<C[i][j]<< endl; ///
                sum[i][j] = 0;
            }
        }
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                if(C[i][j] == 0 && i >= j) sum[i][j] = 1;
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1] + sum[i][j] - sum[i - 1][j - 1];
                // cout << i <<' '<<j<<' '<<sum[i][j]<< endl; ///
            }
        }
    while(t--) {
        scanf("%d%d", &n, &m);
        printf("%d\n", sum[n][m]);
    }
    system("pause");
    return 0;
}

组合计数(包含容斥原理)

例1

x1+x2+x3A 的非负整数解的组数(A1e9

方法1:

ans=x1+x2+x3iiA 的方案数 会tle

方法2:

ans=x1+x2+x3+x4=A 的方案数 OK!!

例2

方程 x1+x2+x3=13 有多少组解? 其中 xi<6

——容斥原理

答案 = 全集 - (恰好满足每个xi<6 出现一次的方案数)

ans=C152(3C923C32+0)

一般情况见例4

例3

x1+x2+...+xn=m,满足0xik 方案数

ans = Cn+m1n1 减去 不合法情况(xik+1

不合法情况可以根据容斥原理算出:Cn1Cn+m1(k+1)n1Cn2Cn+m12(k+1)n1+...

时间复杂度 O(n)

例4

x1+x2+...+xn=m,满足最大的 xi 刚好是 k 的方案数

ans = {xik} 的方案数 减去 {xik1} 的方案数

用两次容斥原理即可。

例题:2021 CCPC 威海 M

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10, mod = 998244353;
int n, m, k;
ll fac[N], inv[N];

ll quickpow(ll a, ll x) {  //底数也开ll  因为可能是inv[] 
	ll ans = 1;
	while(x) {
		if(x & 1) {
			ans *= a;	
			ans %= mod;
		}
		a *= a;
		a %= mod;
		x /= 2;
	}
	return ans;
}

void init(int n) {
	fac[0] = 1;
	for(int i = 1; i <= n; i++) {
		fac[i] = fac[i - 1] * i % mod;
	}
	inv[n] = quickpow(fac[n], mod - 2);  
	for(int i = n - 1; i >= 1; i--) {
		inv[i] = inv[i + 1] * (i + 1) % mod;  
	}
	inv[0] = 1;
}

ll C(int n, int m) {
    if(n < m) return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

inline ll solve(int k) {
    ll ans = C(n, n - m);
    for(int i = 1; i * k <= min(n, m); i++) {
        if(i % 2) ans = (ans - C(n - m + 1, i) * C(n - k * i, n - m) % mod + mod) % mod;
        else ans = (ans + C(n - m + 1, i) * C(n - k * i, n - m) % mod) % mod;
    }
    return ans;
}

int main() {
    init(1e5);
    scanf("%d%d%d", &n, &m, &k);
    if(k == 0) {
        if(m == 0) puts("1");
        else puts("0");
        system("pause");
        return 0;
    }
    if(n < m || m < k) {
        puts("0");
        system("pause");
        return 0;
    }
    printf("%lld\n", (solve(k + 1) - solve(k) + mod) % mod);
    system("pause");
    return 0;    
}
例5

HAOI2008 硬币购物

题意

n 种物品,给出它们的价值,t 组询问,每次给出这 n 种物品的数量,和想购买东西的价值。问要想买到这些东西,共有多少种方案数?

题解

假设没有数量的限制,那么就是一个完全背包问题

再根据容斥原理(不合法按奇加偶减)减去数量超过的方案数就是最终的答案

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int n, m, k, q, s;
int c[5], d[5];
ll f[N];  //价值为i的方案数

void init() {    // 完全背包
    f[0] = 1;
    for(int i = 0; i < 4; i++) {
        for(int j = c[i]; j <= 100000; j++) {
            f[j] += f[j - c[i]];
        }
    }
}

inline ll solve() {
    ll ans = 0;
    for(int S = 0; S < (1 << 4); S++) {
        int cnt = __builtin_popcount(S);
        ll tem = 0;
        for(int j = 0; j < 4; j++) {
            if(S & (1 << j)) {
                tem += 1ll * (d[j] + 1) * c[j];
            }
        }
        if(s < tem) continue;
        if(cnt % 2) ans -= f[s - tem];
        else ans += f[s - tem];
    }
    return ans;
}

int main() {
    for(int i = 0; i < 4; i++) cin >> c[i];
    init();
    cin >> q;
    while(q--) {
        for(int i = 0; i < 4; i++) cin >> d[i];
        cin >> s;
        printf("%lld\n", solve());
    }
    system("pause");
    return 0;    
}

生成函数

普通型生成函数

可以理解成 有无限项的数列 的系数

母函数 O(n2) 模板
#include<cstdio>
using namespace std;
const int N = 1020;
int c1[N],c2[N];
int w[N];
int n;
int main(){
	while(scanf("%d",&n)==1&&n){
		for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
		for(int i=0;i<=n;i++){
			c1[i]=0,c2[i]=0;
		}
		for(int i=0;i<=n;i++){
			c1[i]=1; 
		}
		for(int i=2;i<=n;i++){
			for(int j=0;j<=n;j++){
				for(int k=0;j+k<=n&&k<=w[i];k++){
					c2[j+k]+=c1[j];
				}
			}
			for(int k=0;k<=n;k++){
				c1[k]=c2[k],c2[k]=0;
			}
		}
		printf("%d\n",c1[n]);
	}
	return 0;
}



公式

· 11x=i=0xi

所以 1(1x)nxk 的系数是 Cn+k1n1 = Cn+k1k
(可以用类似 组合数学 y1+y2+...+yn=k(yi0) 求方案数 来表示)

指数型生成函数

在数列的基础上,xi 还要除以 i! ,表示排列和顺序无关

指数型母函数 O(n2) 模板
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 15, mod = 1e9 + 7;
int n, m;
int num[N];
int f[N];
double c1[N], c2[N];

int main() {
    f[0] = 1;
    for(int i = 1; i <= 10; i++) f[i] = f[i - 1] * i;
    while(scanf("%d%d", &n, &m) == 2) {
        for(int i = 1; i <= n; i++) scanf("%d", &num[i]);
        for(int i = 0; i <= n; i++) {
            c1[i] = 0;
            c2[i] = 0;
        }
        for(int i=0;i<=num[1];i++){
			c1[i] = 1.0 / f[i]; 
		}
        for(int i=2;i<=n;i++){
			for(int j=0;j<=10;j++){
				for(int k=0;j+k<=10&&k<=num[i];k++){
					c2[j+k] += 1.0 * c1[j] / f[k];
				}
			}
			for(int k=0;k<=10;k++){
				c1[k]=c2[k], c2[k]=0;
			}
		}
        printf("%.0f\n", c1[m] * f[m]);
    }
    system("pause");
    return 0;
}

鸽巢原理

例1

POJ 3370

题意

给你 n 个数,你需要找到这些数中的一些,满足这些数的和是 c 的倍数,并输出这些数的下标

题解

乍一看感觉好像是 O(n2) 的,但是并不需要,只要预处理出前缀和数组

如果 sum[i]modc==0 ,那么直接输出 [1,i] 即可;如果 sum[i]modc==sum[j]modc,输出 [i+1,j] 即可

posted @   starlightlmy  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示