ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

组合数学

2022-07-31 10:05:30  阅读:174  来源: 互联网

标签:return 组合 int LL times 数学 include MOD


组合数学题目选做

一、[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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有