《算法笔记》学习日记——5.7 扩展欧几里得算法&5.8 组合数

5.7 扩展欧几里得算法

Codeup Contest ID:100000594
2020.3.30:今天好像Codeup变得没以前那么卡了??(大概)

问题 A: 同余方程-NOIP2012TGD2T1

题目描述
求关于x的同余方程ax≡1(mod b)的最小正整数解。
输入格式
每组输入数据只有一行,包含两个正整数a, b,用一个空格隔开。
数据规模:
对于40%的数据,2≤b≤1,000;
对于60%的数据,2≤b≤50,000,000;
对于100%的数据,2≤a, b≤2,000,000,000。
输出
每组输出只有一行,包含一个正整数x0,即最小正整数解。输入数据保证一定有解。
样例输入

3 10

样例输出

7

思路
这题按照书上写的来就行了,为了得到同余方程ax≡1(mod b)的最小正整数解,因此,需要先计算ax+by=gcd(a,b)的最小非负整数解(一定要在这里解出最小非负整数解,因为如果x是负数的话,且同余方程只有一个解,那么就得不到最小正整数解了),然后再通过公式把x0转换成x,得到ax+by=c的其中一组解,最后遍历K的值(0~gcd(a,m)-1)来寻找最小正整数解即可。
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<algorithm>
using namespace std;
int gcd(int a, int b){
	if(b==0) return a;
	else return gcd(b, a%b);
}
int exGcd(int a, int b, int &x, int &y){
	if(b==0){
		x = 1;
		y = 0;
		return a;
	}
	int g = exGcd(b, a%b, x, y);
	int temp = x;
	x = y;
	y = temp-a/b*y;
	return g; 
}
int main(){
	int a, b;
	while(scanf("%d%d", &a, &b) != EOF){
		int x = 0, y = 0;
		exGcd(a, b, x, y);
		x = (x%b/gcd(a,b)+b/gcd(a,b))%(b/gcd(a,b));//先得到ax+by=gcd(a,b)的最小非负整数解,得到x0 
		x = 1*x/gcd(a, b);//再通过公式把x0转换成x,得到ax+by=c的其中一组解 
		int bound = gcd(a, b)-1;
		for(int i=0;i<=bound;i++){
			x += b/gcd(a, b)*i;
			if(x>0) break;
		}
		printf("%d\n", x);
	}
	return 0;
}

小结

这一节的内容偏向数学性的更多一点,对于我这种数学不好的渣渣来说只是体验一下同余方程的解法而已……

5.8 组合数

Codeup Contest ID:100000595

问题 A: 计算组合数

题目描述
编制程序,输入m,n(M>=n>=0)后,计算下列表达式的值并输出:
m ! n ! ( m n ) ! \frac{m!}{n! (m-n)!}
要求将计算阶乘运算的函数写为fact(n),函数返回值的类型为float
输入
m n
输出
对应表达式的值
样例输入

2 1

样例输出

2

思路
这题就是算C(m,n)的值,只不过和正常写法C(n,m)倒了一下。题目要求写阶乘函数,那这题就只好用定义式来计算组合数了。
不过需要注意的是,用定义式的阶乘方法来计算组合数的话,得到的组合数范围很小(因为阶乘会容易超数据类型范围)。
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<algorithm>
using namespace std;
float fact(int n){
	if(n==0) return 1;
	else return fact(n-1)*n;
}
int main(){
	int m, n;
	while(scanf("%d%d", &m, &n) != EOF){
		printf("%.0f\n", fact(m)/(fact(n)*fact(m-n)));
	}
	return 0;
}

问题 B: 求组合数

题目描述
组合数的计算虽说简单但也不乏有些陷阱,这主要是因为语言中的数据类型在表示范围上是有限的。更何况还有中间结果溢出的现象,所以千万要小心。
输入
求组合数的数据都是成对(M与N)出现的,每对整数M和N满足0<m, n≤20,以EOF结束。
输出
输出该组合数。每个组合数换行。
样例输入

5 2
18 13

样例输出

10
8568

思路
这题显然也是跟上题一样是倒过来的C(m,n),但是不影响我们做题。因为组合数的递归写法比较简单清晰,我这里就采用了递归的方式来做。
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<algorithm>
using namespace std;
long long res[67][67]={0};//存储计算过的C(n,m),避免重复计算 
long long C(long long n, long long m){
	if(m==0||m==n) return 1;
	if(res[n][m]!=0) return res[n][m];
	return res[n][m] = C(n-1, m) + C(n-1, m-1);
}
int main(){
	int m, n;
	while(scanf("%d%d", &m, &n) != EOF){
		printf("%d\n", C(m,n));
	}
	return 0;
}

小结

这一节其实讲了很多组合数计算的方法,以及很多C(n,m)%p的方法,有兴趣的可以了解一下,我个人认为还是用递归的方法比较方便,简单又好记,取模的话直接在递归函数的最后加上%p就行了。

发布了54 篇原创文章 · 获赞 27 · 访问量 4991

猜你喜欢

转载自blog.csdn.net/weixin_42257812/article/details/105195333
今日推荐