其他分享
首页 > 其他分享> > 组合数学

组合数学

作者:互联网

组合数学题目选做

一、[USACO09FEB]Bulls And Cows S

题目链接:[USACO09FEB]Bulls And Cows S

Solution1

由题目可以很快想到DP解决,于是设 \(f_i\) 表示长度为 \(i\) 的排列的合法方案数,那么:

\[f_i=f_{i-1}+f_{i-k-1} \]

\(f_{i-1}\) 即位置 \(i\) 不选公牛,那么可以加上前一位所有的方案数;\(f_{i-k-1}\) 表示位置 \(i\) 选公牛,那么只能加上前 \(k-1\) 位的方案数。

预处理,

\[f_i=i+1(0\le i\le k) \]

Code

#include <iostream>

using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
const int MOD = 5000011;
LL f[N];
int n, m;

int main () {
    cin >> n >> m;
    for (int i = 0; i <= m; i ++) f[i] = i + 1;
    for (int i = m + 1; i <= n; i ++) {
        f[i] += f[i - 1] + f[i - m - 1];
        f[i] %= MOD;
    }
    cout << f[n];
    return 0;
}

Solution2

既然是组合题,怎么能少得了组合数硬搞呢?

考虑每次枚举放 \(m\) 只公牛,那么为了使方案合法,首先要在每头公牛之间放 \(k\) 只奶牛,即 \((m-1)\times k\) 只奶牛。那么还剩 \(n-(m-1)\times k\) 只奶牛,我们将其插入每头公牛的两侧,即 \(m+1\) 个位置,于是可以转化为:在 \(m+1\) 个不同盒子中放 \(n-(m-1)\times k\) 个相同球的问题,方案数为:

\[C_{盒子+球-1}^{盒子-1} \]

然后就做完了。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int N = 2e5;
const LL MOD = 5000011;
int n, k;
LL ans = 1, fac[N];

void init() {
	fac[0] = 1;
	for (int i = 1; i <= 1e5; i ++) fac[i] = fac[i - 1] * i % MOD;
}

LL ksm(LL a, LL b) {
	LL sum = 1;
	while (b) {
		if (b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return sum;
}

LL inv(LL x) {
	return ksm(x, MOD - 2);
}

LL C(LL n, LL m) {
	if (n < m) return 0;
	LL fm = fac[m] * fac[n - m] % MOD, fz = fac[n];
	return fz * inv(fm) % MOD;
}

int main() {
	cin >> n >> k;
	init();
	for (int i = 1; i <= n; i ++) {
		LL ball = n - i - (i - 1) *  k;
		LL box = i + 1;
		ans += C(box + ball - 1, box - 1);
		ans %= MOD;
	}
	cout << ans;
	return 0;
}

二、方程的解

题目链接:方程的解

Solution

按照题目实现,用插板法算出答案是 \(C_{g-1}^{k-1}\),可以用递推算组合数,高精度加法更好实现。

Code

#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const LL MOD = 1000;
const int N = 11000;
LL k, x, fac[N];
string f[1001][101];
int a[1000], b[1000], c[1000];

LL ksm(LL a, LL b) {
	a %= MOD;
	LL sum = 1;
	while (b) {
		if (b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return sum;
}

string add(string s1, string s2) {
	memset (a, 0, sizeof(a));
	memset (b, 0, sizeof(b));
	int l1 = s1.size(), l2 = s2.size();
	for (int i = 1; i <= l1; i ++) a[i] = int(s1[l1 - i] - '0');
	for (int i = 1; i <= l2; i ++) b[i] = int(s2[l2 - i] - '0');
	
	int l3 = 1, x = 0;
	while (l3 <= l1 || l3 <= l2) {
		c[l3] = a[l3] + b[l3] + x;
		x = c[l3] / 10;
		c[l3] %= 10;
		l3 ++;
	}
	c[l3] = x;
	while (!c[l3]) l3 --;
	string s3;
	s3.clear();
	for (int i = l3; i >= 1; i --) s3 += char(c[i] + '0');
	return s3;
}

LL C(LL n, LL m) {
	f[0][0] = "1"; f[1][0] = "1";
	for (int i = 1; i <= n; i ++)
		for (int j = 0; j <= m; j ++) {
			if (!j) {
				f[i][j] = "1";
				continue;
			}
			if (j > i) break;
			if (i - 1 < j) f[i][j] = f[i - 1][j - 1];
			else f[i][j] = add(f[i - 1][j], f[i - 1][j - 1]);
		}
}

int main() {
	cin >> k >> x;
	LL g = ksm(x, x);
	g --, k --;
	
	C(g, k);
	
	cout << f[g][k] << endl;
	return 0;
}

三、车的放置

题目链接:车的放置

Solution

考虑将网格棋盘分成两部分,分成两个矩形。即:

Whiteboard

那么,枚举 \(i\) 表示在区域 \(2\) 放的车的个数,方案数是 \(C_{c}^{i}\times A_{d}^{i}\);那么 \(k-i\) 就是区域 \(1\) 车的个数,方案数就是 \(C_{a}^{k-i} \times A_{b+d-i}^{k-i}\),由乘法原理可得:

\[C_{c}^{i}\times A_{d}^{i}\times C_{a}^{k-i} \times A_{b+d-i}^{k-i} \]

将所有合法方案累加起来,得到 \(ans\)。

为什么不枚举 \(i\) 表示区域 \(1\) 的车数呢?因为区域 \(1\) 比区域 \(2\) 高,所以不能确定区域 \(1\) 的车有没有占用区域 \(2\) 的位置,但是枚举区域 \(2\) 就不存在这个问题。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int N = 2100;
const LL MOD = 100003;
int a, b, c, d, k; 
LL fac[N];

void init() {
	fac[0] = 1;
	for (int i = 1; i <= 2000; i ++) fac[i] = fac[i - 1] * i % MOD;
}

LL ksm(LL a, LL b) {
	LL sum = 1;
	while (b) {
		if (b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return sum;
}

LL inv(LL x) {
	return ksm(x, MOD - 2);
}

LL C(LL n, LL m) {
	if (n < m) return 0;
	LL fz = fac[n], fm = fac[m] * fac[n - m] % MOD;
	return fz * inv(fm) % MOD;
}

LL A(LL n, LL m) {
	if (n < m) return 0;
	LL fz = fac[n], fm = fac[n - m];
	return fz * inv(fm) % MOD;
}

int main() {
	init();
	cin >> a >> b >> c >> d >> k;
	
	LL ans = 0;
	for (int i = 0; i <= min(k, min(c, d)); i ++) {
		if (k - i > a) continue;
		ans += C(c, i) % MOD * A(d, i) % MOD * C(a, k - i) % MOD * A(b + d - i, k - i) % MOD;
		ans %= MOD;
	}
	cout << ans << endl;
	return 0;
}

四、[CQOI2014]数三角形

题目链接:[CQOI2014]数三角形

Solution

我们很容易将所有三个点组合的方案算出来,即 \(C_{n\times m}^{3}\),注意这里的 \(n,m\) 表示的是点数,所以要将题目给的加一,所以考虑求出不合法的方案数,从题目中不难发现不合法的方案数就是三点共线的情况,而这条“线”也存在斜率(设为 \(k\))不同的情况,分类讨论:

  1. \(k=0\) :总共 \(n\) 行,每行 \(m\) 个数,所以是 \(n\times C_{m}^{3}\)
  2. \(k=+\infty\) :同理,\(m\times C_{n}^{3}\)
  3. \(k>0\):具体讨论如下

首先想到枚举三角形三个点中左下角的坐标 \((i,j)\) (横,纵坐标),那么右上角的坐标就有 \((m-i)\times (n-j)\) 种取法,但是这样枚举我们不方便求出中间可以取多少个点,所以改变枚举策略。

枚举左下角和右上角的差距 \((i,j)\) ,那么中间点的取值有 \(gcd(i,j)-1\) 种方案(可以用类似向量数乘的思想证明),左下角的取值有 \((m-i)\times (n-j)\) 种方案,总方案就是:

\[(gcd(i,j)-1)\times (m-i)\times (n-j) \]

减去即为答案。

  1. \(k<0\) :可以看做 \(k>0\) 的情况翻折得出,算数一样。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
LL n, m;
LL ans;

LL C(LL n) {
	return (n - 2) * (n - 1) * n / 6;
}

int gcd(int a, int b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}

int main() {
	cin >> m >> n;
	
	m ++, n ++;
	ans = C(m * n);
	ans = ans - n * C(m) - m * C(n);
	
	for (LL i = 1; i <= m; i ++)
		for (LL j = 1; j <= n; j ++) {
			ans -= (gcd(i, j) - 1) * (m - i) * (n - j) * 2;
		}
	
	cout << ans;
	return 0;
}

五、序列统计

题目链接:序列统计

Solution

题目可以转化为在一个多重集中选组合的问题,具体说来,多重集就是 \({L,L+1,L+2,...,}R\) 其中每种元素有无限多个,从中选出 \(N\) 个,那么单调不降序列就可以看为一种组合,而不是排列,那么长度为 \(N\) 的长度方案数就是:

\[C_{N + R - L + 1-1}^{R - L +1-1} \]

即:

\[C_{N + R - L}^{R - L} \]

那么题目要求的是:

\[\sum_{i=1}^{N}C_{i + R - L}^{R - L} \]

设 \(R-L\) 为 \(m\),原式转化为:

\[\sum_{i=1}^{N}C_{i+m}^{m} \]

将式子展开,写成:

\[C_{m+1}^{m}+C_{m+2}^{m}+C_{m+3}^{m}+...+C_{N+m}^{m} \]

那么组合的化式子一般用 \(C_{n}^{m}=C_{n-1}^{m-1}+C_{n-1}^{m}\)。

于是在前面添一个项 \(C_{m+1}^{m+1}\),转化为:

\[\begin{aligned} &\;\;\;\;\; C_{m+1}^{m+1}+C_{m+1}^{m}+C_{m+2}^{m}+...+C_{N+m}^{m}-C_{m+1}^{m+1}\\ &=C_{m+2}^{m+1}+C_{m+2}^{m}+...+C_{N+m}^{m}-1\\ &=C_{m+3}^{m+1}+...+C_{N+m}^{m}-1\\ &=C_{N+m+1}^{m+1}-1 \end{aligned} \]

所以,\(ans=C_{N+R-L+1}^{R-L+1}-1\)。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const LL MOD = 1e6 + 3;
const int N = 2e6 + 10;
int t;
LL n, l, r, k;
LL fac[N];

void init () {
	fac[0] = 1;
	for (int i = 1; i <= 2e6; i ++) fac[i] = fac[i - 1] * i % MOD;
}

LL ksm(LL a, LL b) {
	LL sum = 1;
	while (b) {
		if (b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return sum;
}

LL inv(LL x) {
	return ksm(x, MOD - 2);
}

LL C(LL n, LL m) {
	if (n < m) return 0;
	LL fz = fac[n], fm = fac[n - m] * fac[m] % MOD;
	return fz * inv(fm) % MOD;
}

LL Lucas (LL n, LL m) {
	if (!m) return 1;
	return C(n % MOD, m % MOD) * Lucas(n / MOD, m / MOD) % MOD;
}

int main() {
	init();
	cin >> t;
	while (t--) {
		cin >> n >> l >> r;
		k = r - l;
		cout << (Lucas(k + n + 1, k + 1) - 1 + MOD) % MOD << endl;
	}
	return 0;
}

六、[SHOI2015]超能粒子炮·改

题目链接:[SHOI2015]超能粒子炮·改

Solution

题目要求:

\[\sum_{i=0}^{k}C_{n}^{i} \]

由于是组合数化式子,想到两条性质:

  1. \(C_{n}^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\)
  2. \(C_{n}^{m}=C_{n\;mod\;p}^{m\;mod\;p}\times C_{n/p}^{m/p}\)

发现每一项下面的 \(n\) 是定值,所以用第一条性质不好搞,那么用第二条(卢卡斯定理)(这个数据范围也能想到用卢卡斯)。

设 \(f(n,k)\) 表示 \(\sum_{i=0}^{k}C_{n}^{i}\)。则原式可化为:

\[\begin{aligned} &\;\;\;\;\; f(n,k)\\ &=\sum_{i=0}^{k}C_{n}^{i}\\ &=\sum_{i=0}^{k}(C_{n\;mod\;p}^{i\;mod\;p}\times C_{n/p}^{i/p})\\ \end{aligned} \]

看到形如 \(i/p\) 的式子,我们很自然地联想到整除分块(当然,标准的整除分块更难),于是:

\[\begin{aligned} &\;\;\;\;\;\sum_{i=0}^{k}(C_{n\;mod\;p}^{i\;mod\;p}\times C_{n/p}^{i/p})\\ &=\sum_{i=0}^{p-1}(C_{n\;mod\;p}^{i\;mod\;p}\times C_{n/p}^{0})+\sum_{i=0}^{p-1}(C_{n\;mod\;p}^{i\;mod\;p}\times C_{n/p}^{1})+...+\sum_{i=0}^{k\;mod\;p}(C_{n\;mod\;p}^{i\;mod\;p}\times C_{n/p}^{k/p})\\ &=(\sum_{i=0}^{p-1}C_{n\;mod\;p}^{i}) \times (C_{n/p}^{0}+C_{n/p}^{1}+...+C_{n/p}^{k/p-1})+C_{n/p}^{k/p}\times \sum_{i=0}^{k\;mod\;p}C_{n\;mod\;p}^{i} \\ &=f(n\;mod\;p,p-1)\times f(n/p,k/p-1)+C_{n/p}^{k/p}\times f(n\;mod\;p,k\;mod\;p) \end{aligned} \]

\(C_{n/p}^{k/p}\) 可以用 \(Lucas\) 定理求;\(f(n\;mod\;p,p-1)\) 和 \(f(n\;mod\;p,k\;mod\;p)\) 的两个维始终小于 \(p\),所以用 \(f\) 函数的定义式预处理,注意处理边界。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const LL MOD = 2333;
const int N = 3000;
int t;
LL n, k;
LL c[N][N], f[N][N];

void init() {
	c[0][0] = 1;
	for (int i = 1; i <= MOD + 10; i ++) {
		for (int j = 0; j <= i; j ++) {
			if (j == 0) c[i][j] = 1;
			else if (i < j - 1) c[i][j] = c[i - 1][j - 1];
			else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % MOD;
		}
	}
	
	for (int i = 0; i <= MOD + 10; i ++) {
		for (int j = 0; j <= MOD + 10; j ++)
			if (j == 0) f[i][j] = c[i][j];
			else f[i][j] = f[i][j - 1] + c[i][j], f[i][j] %= MOD;			
	}	
}

LL ksm(LL a, LL b) {
	LL sum = 1;
	while (b) {
		if (b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return sum;
}

LL Lucas (LL n, LL m) {
	if (m == 0) return 1;
	if (n == m) return 1;
	if (n < m) return 0;
	return c[n % MOD][m % MOD] * Lucas(n / MOD, m / MOD) % MOD;
}

LL F(LL n, LL k) {
	if (n < 0 || k < 0) return 0;
	if (n == 0 || k == 0) return 1;
	if (n <= MOD && k <= MOD) return f[n][k];
	
	return (F(n / MOD, k / MOD - 1) % MOD * f[n % MOD][MOD - 1] % MOD + Lucas(n / MOD, k / MOD) * f[n % MOD][k % MOD] % MOD) % MOD;
}

int main() {
	ios::sync_with_stdio(0);
	
	init();
	
	cin >> t;
	while (t --) {
		cin >> n >> k;
		cout << F(n, k) << endl;
	}
	
	return 0;
}

七、[HNOI2009]有趣的数列

题目链接:[HNOI2009]有趣的数列

Solution

性质1规定了数列元素的范围,没什么好说;性质2规定了奇数项和偶数项是唯一的排列,可以看做组合,虽然意义不同,但数值上是相同的,那么题目就转化为奇数项和偶数项的取值问题。

设一位数若取为奇数项,则记为 \(0\);取为偶数项,记为 \(1\),比如:

1 2 3 4 5 6
记作
0 1 0 1 0 1

1 2 3 5 4 6
记作
0 1 0 0 1 1

初步观察可以发现,任一前缀的0的个数总是大于等于1的个数,这和任一后缀的1的个数总是大于等于0的个数是等价的,我们对后一句话进行证明。

考虑反证,若存在一个后缀的0的个数大于1的个数,即至少存在一个奇数位,没有比其大的偶数位的数字与其匹配,与题目矛盾,故原命题成立。

而不难想到,这就是卡特兰数,所以题目就是让我们求:

\[Cat_n\;mod\;p \]

展开:

\[\begin{aligned} C_{n}^{2n}/(n+1)=C_{2n}^{n}-C_{2n}^{n-1}=\frac{(2n)!}{n!n!}-\frac{(2n)!}{(n+1)!(n-1)!} \end{aligned} \]

由于这道题没有设模数,所以只能将阶乘转化为算数基本定理的形式,即:

\[x=p_1^{c_1}p_2^{c_2}...p_k^{c_k} \]

然后将除法转化为对应质因子的指数相减,阶乘的快速分解质因数可以参考这题

Code

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int N = 2e6 + 10;
LL n, p;
LL f[N];
LL ans1 = 1, ans2 = 1;

vector<LL> pri;

void init() {
	for (LL i = 2; i <= 2e6; i ++) {
		if (!f[i]) {
			pri.push_back(i);
			for (LL j = 2; j * i <= 2e6; j ++) {
				f[i * j] = 1;
			}			
		}
	}
}

LL count(LL n, LL pr) {
	LL ans = 0;
	while (n) {
		ans += LL(n / pr);
		n /= pr;
	}
	return ans;
}

LL ksm(LL a, LL b) {
	LL sum = 1;
	while (b) {
		if (b & 1) sum = sum * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return sum;
}

int main() {
	ios::sync_with_stdio(0);
	init();
	cin >> n >> p;

	for (LL i = 0; i < pri.size(); i ++) {
		if (pri[i] > 2 * n) break;
		LL pr = pri[i];
		LL num1 = count(2 * n, pr), num2 = count(n, pr), num3 = count(n - 1, pr), num4 = count(n + 1, pr);
		
		ans1 *= ksm(pri[i], num1 - 2 * num2) % p;
		ans1 %= p;
		
		ans2 *= ksm(pri[i], num1 - num3 - num4) % p;
		ans2 %= p;
	}
	
	cout << ((ans1 - ans2) % p + p) % p << endl;
	return 0;
}

标签:return,组合,int,LL,times,数学,include,MOD
来源: https://www.cnblogs.com/zhangyuzhe/p/16536476.html