一.扩展欧几里得
扩展欧几里得做了一件什么事情呢,实际上对于下面那么一个问题
扩展欧几里得会求得一组解x,y使得等式成立;
证明如下:
首先由裴蜀定理可知,一定存在一组解使得等式成立;
其次根据辗转相除法可以递推出:
代码如下:
#include<iostream>
using namespace std;
void exgcd(int a, int b, int &x, int &y){
if(b==0){
x=1;
y=0;
return ;
}
int x1,y1;
exgcd(b,a%b,x1,y1);
x=y1;
y=x1-a/b*y1;
return ;
}
int main(){
int n;
cin>>n;
while(n--){
int a,b,x,y;
cin>>a>>b;
exgcd(a,b,x,y);
cout<<x<<' '<<y<<endl;
}
}
下面还是看一道扩展欧几里得的题目:
我们做一下变形,我们可以知道
ax-b=km,那么ax-km=b,将y=-k,那么ax+my=b;
如果等式成立,那么b一定是gcd(a,m)的倍数。否则一定不存在解
那么一看,这不就是模板吗,直接用即可。
二.中国剩余定理
实际上中国剩余定理描述了这么一个事情,
注意到,这里要求模数必须是两两互质的,不要求两两互质的是扩展中国剩余定理;
在解决中国剩余定理前,我们先了解两种求逆元的方法:
1.快速幂求逆元(要求模数为质数)
显然对于ax=1(mod p)
ax=(mod p)
于是x=(mod p);
2.exgcd求逆元(要求gcd(a,p)=1)
显然对于ax=1(mod p)
ax-kp=1
令y=-k
那么ax+py=1,利用扩展欧几里得算法可求出逆元x
了解了以上两个求逆元后,我们可以开始做中国剩余定理了
算法步骤:
代码:注意不可使用快速幂求逆元,因为本题只是说两两互质,并不是说是质数,同时本题需要开int128才能过hack1
#include<bits/stdc++.h>
using namespace std;
#define int long long
using i128 = __int128_t;
std::ostream &operator<<(std::ostream &os, i128 n) {
std::string s;
while (n) {
s += '0' + n % 10;
n /= 10;
}
std::reverse(s.begin(), s.end());
return os << s;
}
inline int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
x = (x << 1ll) + (x << 3ll) + (c - '0');
c = getchar();
}
return x * f;
}
signed main() {
auto exgcd = [&](auto self, i128 a, i128 b, i128& x, i128& y) ->i128 {
if (b == 0ll) {
x = 1ll; y = 0ll;
return a;
}
i128 r = self(self, b, a % b, x, y);
i128 temp = x; x = y; y = temp - (a / b) * y;
return r;
};
int n = read();
vector<i128> a(n), p(n);
for (int i = 0; i < n; i++) {
p[i] = read();
a[i] = read();
}
auto CRT = [&]() -> i128 {
i128 M = 1ll;
for (int i = 0; i < n; i++) {
M = M * p[i];
}
i128 ans = 0;
for (int i = 0; i < n; i++) {
i128 minv = M / p[i];
i128 v = 0, k = 0;
exgcd(exgcd, minv, (i128)p[i], v, k);
v = (v % p[i] + p[i]) % p[i];
ans = (ans + (minv * a[i] % M) * v % M) % M;
}
return ans;
};
cout << CRT() << endl;
return 0;
}
下面看一题扩展中国定理(不要求两两互质)
大致思路是:先对两组数据求解,依次进行;
同时
这里我只对为什么通解是lcm(a1,a2)做解释,这是由于x同余m1(mod a1),那么也就是说求出了x的特解后,通解加上的数必然是a1的倍数,其次对于x同余m2(mod a2);那么通解加上的数必然是a2的倍数;综合来看最小的倍数就是(a1,a2)的最小公倍数。
代码如下:注意开int128
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int N = 1e5 + 10;
ll x, y, d; int n;
void exgcd(ll &x, ll &y, ll a, ll b) {
if(!b) d = a, x = 1, y = 0;
else exgcd(y, x, b, a % b), y -= a / b * x;
}
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}
ll lcm(ll a, ll b) {
return a / gcd(a, b) * b;
}
ll a, b, A, B;
void merge() {
exgcd(x, y, a, A);
ll c = B - b;
if(c % d) puts("-1"), exit(0);
x = x * c / d % (A / d);
if(x < 0) x += A / d;
ll mod = lcm(a, A);
b = (a * x + b) % mod; if(b < 0) b += mod;
a = mod;
}
int main() {
scanf("%d", &n);
for(int i = 1 ; i <= n ; ++ i) {
long long _A, _B;
scanf("%lld%lld", &_A, &_B), A = _A, B = _B;
if(i > 1) merge();
else a = A, b = B;
}
printf("%lld\n", (long long)(b % a));
}
三.组合数
组合数的求法:
1.递推式
2.逆元(注意快速幂求逆元的时候需要注意模数需要为质数)
3.lucas(要求模数为质数,实际上可以证明出来)
这里给出证明的链接卢卡斯定理 - OI Wiki
代码模板:
#include<iostream>
using namespace std;
typedef long long LL;
int qmi(int a, int b, int p){
int res=1;
while(b){
if(b&1)res=(LL)res*a%p;
b>>=1;
a=(LL)a*a%p;
}
return res;
}
LL C(int a, int b, int p){
if(b>a)return 0;
LL res=1;
for(int i=1,j=a; i<=b; i++,j-- ){
res=res*j%p;
res=res*qmi(i,p-2,p)%p;
}
return res;
}
LL lucas(LL a,LL b, LL p){
if(a<p && b<p)return C(a,b,p);
return C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}
int main(){
int n;
cin>>n;
while(n--){
LL a,b,p;
cin>>a>>b>>p;
cout<<lucas(a,b,p)<<endl;
}
}
4.高精度暴力
采用质数分解,由于组合数一定为整数
代码:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int primes[5010];
bool st[5010];
int cnt=0;
int sum[5010];
void get_primes(int a){
for(int i=2; i<=a; i++){
if(!st[i]){
primes[cnt++]=i;
st[i]=true;
}
for(int j=0; i*primes[j]<=a && j<cnt; j++){
st[i*primes[j]]=true;
if(i%primes[j]==0)break;
}
}
}
vector<int> mul(vector<int>v, int b){
vector<int>c;
int len=v.size();
int t=0;
for(int i=0; i<len; i++){
t+=b*v[i];
c.push_back(t%10);
t/=10;
}
while(t){
c.push_back(t%10);
t/=10;
}
return c;
}
int get(int a, int p){
int res=0;
while(a){
res+=a/p;
a/=p;
}
return res;
}
int main(){
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0; i<cnt; i++){
sum[i]=get(a,primes[i])-get(b,primes[i])-get(a-b,primes[i]);
//cout<<primes[i]<<' '<<sum[i]<<endl;
}
vector<int>v;
v.push_back(1);
for(int i=0; i<cnt; i++){
for(int j=0; j<sum[i]; j++){
v=mul(v,primes[i]);
}
}
for(int i=v.size()-1; i>=0; i--)cout<<v[i];
cout<<endl;
return 0;
}
卡特兰数:
采用对称的方法证明,如果有一条边跨过一部分最终去到(n,n),那么一定可以通过对称的方式去到(n-1,n+1);
上述第一点是显而易见的,那么对于任意一条从(0,0)->(n-1,n+1)的路径是否一定经过红线呢,
下面给出理论证明:
实际上对于任何(0,0)->(n-1,n+1)的路径path我们可以知道,一定存在某一时path一定穿过红线,那么记下这个时刻点,这样我们一定可以跟这个时刻点,后面path的方式,唱“反调”,当其到达(n-1,n+1)时,我们到达(n,n);
四.高斯消元
这个直接套模板即可,基本的思想是消元;
步骤如下:
1.找到一行第i列不为0的行r,将其移到第i行(如果找不到就continue)
2.进行消元,依次类推
3.回代求解
题目链接:【模板】高斯消元法 - 洛谷
时间复杂度O(n^3)
注意异或的情况;
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-8;
int n;
double a[N][N];
int gauss() // 高斯消元,答案存于a[i][n]中,0 <= i < n
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ ) // 找绝对值最大的行
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1
for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2; // 无解
return 1; // 有无穷多组解
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[i][j] * a[j][n];
return 0; // 有唯一解
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n + 1; j ++ )
scanf("%lf", &a[i][j]);
int t = gauss();
if (t == 2 || t==1) puts("No Solution");
else
{
for (int i = 0; i < n; i ++ )
printf("%.2lf\n", a[i][n]);
}
return 0;
}
五.容斥原理和博弈论
这两块内容,实际上不好阐述;这里给出要点,读者可以在数学部分简介 - OI Wiki中仔细学习;
等后面有空了我再加上这两部分的详细阐述;
容斥原理的要点为:质数筛,通过+1,-1,+1,-1来实现不重不漏的求解
博弈论的要点是:异或+seg函数,本质上来说是根据游戏规则推出seg函数(采用记忆化搜索);
扩展:还有一系列的比如,扩展欧拉定理,扩展kmp,扩展卢卡斯定理,扩展中国剩余定理;