数学知识汇总

1,排列组合的算法
2,gcd算法

  1. 组合排列
    upload successful
    upload successful
    公式中A(n,m)为排列数公式,C(n,m)为组合数公式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

方法一:DFS实现全排列方法如下:
//全排列问题
#include<stdio.h>
#include<string.h>
int n;
char  a[15];
char re[15];
int vis[15];
//假设有n个字符要排列,把他们依次放到n个箱子中
//先要检查箱子是否为空,手中还有什么字符,把他们放进并标记。
//放完一次要恢复初始状态,当到n+1个箱子时,一次排列已经结束
void dfs(int step)
{
    int i;
    if(step==n+1)//判断边界
    {
        for(i=1;i<=n;i++)
            printf("%c",re[i]);
        printf("\n");
        return ;
    }
    for(i=1;i<=n;i++)//遍历每一种情况
    {
        if(vis[i]==0)//check满足
        {
            re[step]=a[i];
            vis[i]=1;//标记
            dfs(step+1);//继续搜索
            vis[i]=0;//恢复初始状态
        }
    }
    return ;
}
 
int main(void)
{
    int T;
    scanf("%d",&T);
    getchar();
    while(T--)
    {
        memset(a,0,sizeof(a));
        memset(vis,0,sizeof(vis));//对存数据的数组分别初始化
        scanf("%s",a+1);
        n=strlen(a+1);
        dfs(1);//从第一个箱子开始
    }
    return 0;
}


方法二:
* 递归实现全排列
 
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
    
**递归思想:** 
	假如针对abc的排列,可以分成 (1)以a开头,加上bc的排列 (2)以b开头,加上ac的排列 (3)以c开头,加上ab的排列    
    
  
#include<bits/stdc++.h> 
using namespace std;
//产生排列组合的递归写法 
void pai(char t[],int k,int n){
		if(k==n-1){   //输出这个排列,k=n-1就是 递归出口
			for(int i=0;i<n;i++){
				cout<<t[i]<<" ";
			}
			cout<<endl;
		}else{
			for(int i=k;i<n;i++){
				 //一次挑选n个元素中的一个,和前位置替换
				int tmp=t[i];t[i]=t[k];t[k]=tmp;
				//再对其余的n-1个字母一次挑选
				pai(t,k+1,n);
				//在换回到原来的状态 
				tmp=t[i];t[i]=t[k];t[k]=tmp;
			}
		}
	}
int main (){ 
	char t[3]={'a','b','c'};  //t为数组 
	int k=0;  //k为起始排列值 
	int n=3;   //n为数组长度,是数组声明的时候的下标 
	cout<<"第一种方法:"<<endl; 
	pai(t,0,n);
	cout<<"第二种方法:"<<endl;
	do{
		cout<<t[0]<<" "<<t[1]<<" "<<t[2]<<endl;
	}while(next_permutation(t,t+3));//参数3指的是要进行排列的长度 
    /*
    如果存在t之后的排列,就返回true。如果a是最后一个排列没有后继,返回false,每执行一次,t就变成它的后继;若排列本来就是最大的了没有后继,则next_permutation执行后,会对排列进行字典升序排序,相当于循环
    */
}

其中STL之next_permutation函数详解
(1) int 类型的next_permutation
 
int main()
{
	 int a[3];
	 a[0]=1;a[1]=2;a[2]=3;
 	do{
		cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
	} while (next_permutation(a,a+3)); //参数3指的是要进行排列的长度
 
//如果存在a之后的排列,就返回true。如果a是最后一个排列没有后继,返回false,每执行一次,a就变成它的后继
}
/*如果改成 while(next_permutation(a,a+2));只对前两个元素进行字典排序
*/

(2) char 类型的next_permutation
 
int main()
{
	 char ch[205];
	cin >> ch; 
	sort(ch, ch + strlen(ch) );
	//该语句对输入的数组进行字典升序排序。如输入9874563102 cout<<ch; 将输出0123456789,这样就能输出全排列了
 
 char *first = ch;
 char *last = ch + strlen(ch);
 
	 do {
 		cout<< ch << endl;
	}while(next_permutation(first, last));
 return 0;
}
 
//这样就不必事先知道ch的大小了,是把整个ch字符串全都进行排序
//若采用 while(next_permutation(ch,ch+5)); 如果只输入1562,就会产生错误,因为ch中第五个元素指向未知
//若要整个字符串进行排序,参数5指的是数组的长度,不含结束符

(3) string 类型的next_permutation
 
int main()
{
	 string line;
	 while(cin>>line&&line!="#")
	{
	 	 if(next_permutation(line.begin(),line.end())) //从当前输入位置开始
		 cout<<line<<endl;
		 else cout<<"Nosuccesor\n";
	}
}

(4)next_permutation 自定义比较函数
 
#include<iostream> //poj 1256 Anagram
#include<string>
#include<algorithm>
using namespace std;
int cmp(char a,char b) //'A'<'a'<'B'<'b'<...<'Z'<'z'.
{
 	if(tolower(a)!=tolower(b))
	 return tolower(a)<tolower(b);
	 else
 	return a<b;
}
int main(){
	 char ch[20];
	 int n;
	 cin>>n;
	 while(n--)
	{
		scanf("%s",ch);
		sort(ch,ch+strlen(ch),cmp);
		 do
		{
			printf("%s\n",ch);
		}while(next_permutation(ch,ch+strlen(ch),cmp));
	}
 return 0;
}

2.1、gcd算法
   GCD(Great Common Divisor)算法,即所谓最大公约数算法,也称为HCF(Highest Common Factor)算法。而所谓的最大公约数,指的是几个整数中共有约数中最大的一个。如果数A可被M整除,则M就是A的约数(因数),那么如果数A、 B、 C都可被M整除,且M是可以整除这些数种最大的整数,则M就成为A B C的最大公约数,记为(A、 B、 C),如(12、15、18)=3。

  • 辗转相除法  即两个整数的最大公约数等于其中较小的数和两数的差的最大公约数。即一步步的降低两个数的值,直到其中一个变成零,这时所剩下的还没有变成零的数就是两个数的最大公约数。例如:252(2112)和105(215)的最大公约数就是21,则147(252-105)和105的最大公约数也是21,然后42(147-105)和105的最大公约数也是21…………
    定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。最大公约数(Greatest Common Divisor)缩写为GCD。
    gcd(a,b) = gcd(b,a mod b) (不妨设a>b 且r=a mod b ,r不为0)
    证明 :a可以表示成a = kb + r(a,b,k,r皆为正整数,且r<b),则r = a mod b
    假设d是a,b的一个公约数,记作d|a,d|b,即a和b都可以被d整除。
    而r = a - kb,两边同时除以d,r/d=a/d-kb/d=m,由等式右边可知m为整数,因此d|r
    因此d也是b,a mod b的公约数
    假设d是b,a mod b的公约数, 则d|b,d|(a-k*b),k是一个整数。
    进而d|a.因此d也是a,b的公约数
    因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证。
    可以发现,辗转相除法就是一种递归算法,每一步计算的输出值就是下一步计算的输入值。设k表示步骤数(从零开始计数),则计算过程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* 递归方法实现:  */
int gcd(int a,int b){
    if(b==0)
        return a;
    return gcd(b,a%b);
}
/* 迭代法(递推法,即非递归方法):欧几里得算法,计算最大公约数 */
int gcd(int m, int n)
{
    while(m>0)
    {
        int c = n % m;
        n = m;
        m = c;
    }
    return n;
}

/* 快速GCD算法,其原理是:更相减损法,当传入的参数a和b均为偶数时,我们吧a和b全部右移(>>),然后在结尾乘2(<<),如果一个奇数,一个偶数的话我们很容易想到,2肯定不会是这两个数的公约数,所以我们把偶数右移两位(>>), 如果两个数都是奇数的话,我们就令他们进行相减,然后再把a和b取最小的传入,就会出现一奇一偶的两个数即可进行下一步的运算; */
算法C++实现如下:

int qGCD(int a, int b)
{
	if(a == 0) return b;
	if(b == 0) return a;
	if(!(a & 1) && !(b & 1)) // a % 2 == 0 && b % 2 == 0;
		return qGCD(a >> 1, b >> 1) << 1;
	else if(!(b & 1))
		return qGCD(a, b >> 1);
	else if(!(a & 1))
		return qGCD(a >> 1, b);
	else
		return qGCD(abs(a - b), min(a, b));
}


最小公倍数  
最小公倍数,就是a b的乘积除以它们两个的最大公约数,就是它们的最小公倍数。代码如下:

int MinMultiple( int a, int b){
    return (a * b)/gcd(a, b);
}
算法笔记中的数学模块
分数的表示和简化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<bits/stdc++.h>

struct Fraction{
	long long up,down;//分子 分母 
}; 
Fraction reduction(Fraction result){//分数的化简 
	if(result.down<0){//若果分母小于0,让负号表示在分子上 
		result.down=-result.down;
		result.up=-result.up;
	}
	if(result.up==0)result.down=1;//如果分子为0,令分母为1
	else {
		long long d=__gcd(abs(result.up),abs(result.down));
		result.up/=d;
		result.down/=d;
	} 
	return result;
}
//分数的加法
Fraction add(Fraction f1,Fraction f2){
	Fraction result;
	result.up=f1.up*f2.down+f2.up*f1.down;
	result.down=f1.down*f2.down;
	return reduction(result); 
} 
//分数的乘法除法减法类似

void showResult(Fraction r){//分数的输出 
	r=reduction(r);
	if(r.down==1)cout<<r.up<<endl;
	else if(abs(r.up)>r.down){//假分数 
		cout<<r.up/r.down<<' '<<abs(r.up)%r.down<<' '<<r.down<<endl; 
	}else {
		cout<<r.up<<r.down<<endl;
	}
	
}
最大公约数和最小公倍数
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
欧几里得算法基于这个定理:设a、b均为正整数,则gcd(a,b)=gcd(b,a%b) 
*/
int gcd(int a,int b){
	if(b==0)return a;
	else return gcd(b,a%b);
	/*
	return !b?a:gcd(b,a%b);
	*/ 
}
 
//最小公倍数: lcm(a,b)=a*b/gcd(a,b)  
//为防止溢出可写为:a/gcd(a,b)*b
拓展欧几里得算法、同余方程、逆元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
扩展欧几里得算法
给定两个非0整数a,b,求一组整数解(x,y),使得ax+by=gcd(a,b)成立
其中gcd(a,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);//递归计算exgcd(b,a%b)
	int temp=x;
	x=y;
	y=temp-a/b*y;
	return g;//g是gcd 
	/*
	通过x=y(old); y=x(old)-a/b*y(old);来倒推结果   
	tips:a%b=a-(a/b)*b和ax1+by1=bx2+(a%b)y2退出上式 
	*/
} 
通过上面的方法求出的是一组解,
ax+by=gcd(a,b)的通解为:
x'=x+b/gcd*K    (K为任意整数)
y'=y-a/gcd*K  
其中,最小的非负整数解为x=(x%(b/gcd)+b/gcd)%(b/gcd);y同理 

再者:得到ax+by=c的解为(存在解的充要条件为c%gcd==0) 
x'=x+b/gcd*K=cx0/gcd+b/gcd*K;    (K为任意整数,注意周期不能变为cb/gcd^2,因为周期会放大,漏解)
y'=y-a/gcd*K=cy0/gcd-b/gcd*K;

----------------- 
同余式ax==c(mod m)的求解(如果m整除a-b即(a-b)%m==0,那么就说a与b模m同余 )
设a,c,m是整数,其中m>=1,则
1、若c%gcd(a,m)!=0,则同余方程ax==c(mod m)无解
2、若c%gcd(a,m)==0,则同余方程ax==c(mod m)恰好有gcd(a,m)个模m意义下不同的解,
且解的形式为:x'=x+m/gcd(a,m)*K;其中K=0,1,....gcd(a,m)-1,x是ax+my=c的一个解

--------------
逆元 
假设a、b、m是整数,m>1,且有ab==1(mod m)成立,那么就说a和b互为模m的逆元
如果gcd(a,m)!=1,那么同余式ax==1(mod m)无解,a不存在模m的逆元
若果gcd(a,m)==1,那么同余式ax==1(mod m)在(0,m)上有唯一解

int inverse(int a,int m){
	int x,y;
	int g=exgcd(a,m,x,y);
	return (x%m+m)%m;
} 

费马小定理:设m是素数,a是任意整数且a!==0(mod m),则a^(m-1)==1(mod m);
其中a^(m-1)=a*a^(m-2)=1(mod m);得到a^(m-2)%m就是a模m的逆元
素数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
素数=质数,是指除了1和本身之外,不能被其他数整除的一类数。
1既不是素数也不是合数。
 
*/

//素数的判定
bool isPrime(int n){
	if(n<=1)return false;
	//int sqr=(int)sqrt(1.0*n);
	for(int i=2;i*i<=n;i++){
		if(n%i==0)return false;
	}
	return true;
} 

//素数表
 //诶氏筛法
int prime[maxn];
bool isPrime[maxn];

int sieve(int n){//n就是筛子的大小,如1~100的表,那么n=100 
	int num=0;
	memset(isPrime,true,sizeof(isPrime));
	isPrime[0]=isPrime[1]=false;
	for(int i=2;i<=n;i++){//从2开始到n 
		if(isPrime[i]){
			prime[num++]=i;
			for(int j=2;j*i<=n;j++){
				isPrime[j*i]=false;
			}
		}
	}
	return num;
} 

//欧拉筛法
int eulerSieve_1(int n){
	int num=0;
	memset(isPrime,true,sizeof(isPrime));
	isPrime[0]=isPrime[1]=false;
	for(int i=2;i<=n;i++){
		//这个从prime的0下标开始存素数 
		if(isPrime[i])prime[num++]=i;//prine数组从0开始存 
		//第一次执行num++,prime[0]存入i,之后num为1,那么下面这个for中num为1 
		for(int j=0;j<num&&i*prime[j]<=n;j++){
			//这个从j从0开始到num,遍历一遍prime数组,所以遍历的次数为数组个数 
			isPrime[i*prime[j]]=false;
			if(i%prime[j]==0)break;//防止被晒多次 
		}
	}
	return num;
}
质因子分解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//质因子分解
/*
定理:
对于一个正整数n来说,如果他存在[2,n]范围内的质因子,
要么这些质因子全部小与等于sqrt(n);要么只存在一个大于
sqrt(n)的质因子,其余的质因子全部小于等于sqrt(n)。 
*/  

其中int范围内质因子的个数开到10就好了

 
其中有了素数表prime[],Pnum为素数的个数 
结构体数组: 
struct factor{
	int x,cnt;//x为质因子,cnt为个数 
}fac[10]; 

//质因子分解
for(int i=0;i<Punm&&prime[i]<=sqrt(n);i++){
	if(n%prime[i]==0){
		fac[num].x=prime[i];//记录该因子
		fac[num].cnt=0;
		while(n%prime[i]==0){//计算该因子的个数 
			fac[num].cnt++;
			n/=prime[i]; 
		} 
		num++;//不同的质因子的个数 
	}
	if(n==1)break; 
} 
if(n!=1){//如果无法被根号n以内的质因子除尽 
	fac[num].x=n;//那么一定有一个大于根号n的质因子 
	fac[num].cnt=1;
}
高精度加减乘除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//高精度运算
#include<iostream>
#include<string.h> 
using namespace std;
struct bign{
	int d[1000];
	int len;
	bign(){
		memset(d,0,sizeof(d));
		len=0;
	}
};
bign change(char str[]){//将str的整数转为bign 
	bign a;
	a.len=strlen(str);
	for(int i=0;i<a.len;i++){
		a.d[i]=str[a.len-i-1]-'0';//逆着赋值 
	}
	return a;
}
int compare(bign a,bign b){
	if(a.len>b.len)return 1;//a大 
	else if(a.len<b.len)return -1;//a小 
	else {
		for(int i=a.len-1;i>=0;i--){//因为从d的0坐标开始存 
			if(a.d[i]>b.d[i])return 1;
			else if(a.d[i]<b.d[i])return -1;
		}
		return 0;//相等 
	}
} 
bign add(bign a,bign b){
	bign c;
	int carry=0;
	for(int i=0;i<a.len||i<b.len;i++){//以较长的作为界限 
		int temp=a.d[i]+b.d[i]+carry;
		c.d[c.len++]=temp%10;
		carry=temp/10;
	}
	if(carry!=0){
		c.d[c.len++]=carry;//新增一位 
	}
	return c;
}
bign sub(bign a,bign b){
	bign c;
	for(int i=0;i<a.len||i<b.len;i++){
		if(a.d[i]<b.d[i]){
			a.d[i+1]--;//不够减,向高位借 
			a.d[i]+=10;
		}
		c.d[c.len++]=a.d[i]-b.d[i];//计算结果储存起来 
	}
	while(c.len-1>=1&&c.d[c.len-1]==0){
		c.len--;//取出高为的0,并且保留最低位的0 
	}
	return c; 
}
bign multi(bign a,int b){
	bign c;
	int carry;//进位
	for(int i=0;i<a.len;i++){
		int temp=a.d[i]*b+carry;
		c.d[c.len++]=temp%10;
		carry=temp/10;
	} 
	while(carry!=0){//乘法的进位可能不止一位 
		c.d[c.len++]=carry%10;
		carry/=10;
	} 
	return c;
}
bign divide(bign a,int b,int &r){//r为余数
	bign c;
	c.len=a.len;
	for(int i=a.len-1;i>=0;i--){//从高为开始除 
		r=r*10+a.d[i];
		if(r<b)c.d[i]=0;//不够除 
		else {//够除 
			c.d[i]=r/b;
			r=r%b;
		}
	} 
	while(c.len-1>=1&&c.d[c.len-1]==0){
		c.len--;
	}
	return c;
}
void print(bign a){
	for(int i=a.len-1;i>=0;i--){
			cout<<a.d[i];
	}
	cout<<endl;
}
int main(){
	char str1[1000],str2[1000];
	int r=0;
	int c=-2; 
	scanf("%s",str1);
	scanf("%s",str2);
	bign a=change(str1);
	bign b=change(str2);
	cout<<"a是否小于b"<<compare(a,b)<<endl;
	cout<<"a+b="; print(add(a,b)); 
	cout<<"a-b=";   if(compare(a,b)==1)print(sub(a,b));
					else print(sub(b,a)); 
	cout<<"a*c=";
	if(c<0){
		cout<<"-";print(multi(a,-c));		
	}else{
		print(multi(a,c));
	}
	cout<<"a/c=";if(c<0){
		cout<<"-";print(divide(a,-c,r));		
	}else{
		print(divide(a,c,r));
	}cout<<" 余数为:"<<r<<endl;
}
排列组合问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
n!中有(n/p+n/p^2+n/p^3+...)个质因子p,其中/为向下取整
上式等于1~n中p的倍数的个数n/p,加上(n/p)!中质因子p的个数 
int cal(int n,int p){
	int ans=0;
	while(n){
		ans+=n/p;//累加n/p^k 
		n/=p;
	}
	return ans;
} 
或者递归写法:
int cal(int n,int p){
	if(n<p)return 0;
	return n/p+cal(n/p,p);
} 
----------------
组合数计算:
方法二:递推公式计算 (n=67,m=33时溢出) 
long long C(long long n,long long m){
	if(m==0||m==n)return 1;
	return C(n-1,m)+C(n-1,m-1); 
} 
方法三:定义式变形来计算(n=62,m=31时溢出) 
long long C(long long n,long long m){
	long long ans=1;
	for(long long i=1;i<=m;i++){
		ans=ans*(n-m+i)/i;//注意一定要先乘后除 
	} 
	return ans; 
} 


----------
计算Cn m%p
方法二的变形:递归 
int res[1010][1010]={0};
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))%p; 
} 
递推:
void calC(){
	for(int i=0;i<=n;i++){
		res[i][0]=res[i][i]=1;//初始化边界 
	}
	for(int i=2;i<=n;i++){
		for(int j=0;j<=i;j++){//因为有C(i,i-j)=c(i,j),所以计算一半,另一半就计算出来了 
			res[i][j]=(res[i-1][j]+res[i-1][j-1])%p;//递推计算C(i,j)
			res[i][i-j]=res[i][j]; 
		}
	}
}

排列组合详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值