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 组合数
问题 A: 计算组合数
题目描述
编制程序,输入m,n(M>=n>=0)后,计算下列表达式的值并输出:
m
!
n
!
(
m
−
n
)
!
\frac{m!}{n! (m-n)!}
n!(m−n)!m!
要求将计算阶乘运算的函数写为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就行了。