五一集训笔记
参考文献
- 矩阵笔记——孙小麦
- 省选数学基础——张佳瑞
- 清北学堂笔记——赵化哲
- 学习笔记-矩阵——孙亦泽
- 清北学堂五一数学营笔记——孙亦泽
- 胡家瑞的手写笔记
- 高域卬的手写笔记
- LaTex常用技巧6:矩阵编写总结——“小林up”
- LaTeX 数学公式大全——“Iowa_BattleShip”
感谢以上内容,对这份笔记的完善做出巨大贡献。
模运算
这个笔记所有的
带余除法
考虑
c=a/b
,d=a%b
,减法、乘法同理。- 证明:设
,则左式和右式均可以化为 。
- 证明:设
新手题
1
给定 int
范围内。
- 标准错误写法:
#include<iostream>
#define ll long long
using namespace std;
int a,b,c;
int main(){
cin>>a>>b>>c;
cout<<(a+b)%c<<endl;
cout<<(a-b)%c<<endl;\\(1)
cout<<(a*b)%c<<endl;\\(2)
return 0;
}
- 其中
(1)
处 可能小于 ,于是又一种错误的改法出现了:cout<<abs(a-b)%c<<endl;
。- 这么一改,直接把数给变了,答案能对吗??!
- 所以正确的改法为:
cout<<((a-b)%c+c)%c<<endl;
。
(2)
处可能会炸int
,这里可以开long long
,也可以先乘上1ll
,还可以直接用上面的规律。
2
给定
- 又一个标准错误写法:
#include<iostream>
#define ll long long
using namespace std;
ll n,p,ans=1;
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++){
ans*=i;
}
cout<<ans%p;
return 0;
}
- 此处同上面的
(2)
,也会炸int
,而且会炸long long
。于是就只能用规律了。正确代码如下:
#include<iostream>
#define ll long long
using namespace std;
ll n,p,ans=1;
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++){
ans*=i%p;
ans%=p;
}
cout<<ans;
return 0;
}
和
定义:
注:
整除符号
的性质
证明:
于是就有了:
辗转相除法求
使用性质,不断把
代码:
int gcd(int a,int b){
if(b==0)return a;
return gcd(b,a%b);
}
时间复杂度:
- 原因很简单:
时, ,显然小于 ; 时,因为余数小于除数,而除数又不超过 ,所以 ;
- 综上,
,所以时间复杂度不超过 。
求多个数的
的性质与求法
性质:如果
所以求法:
但是
快速幂
给定
首先暴力肯定是不行的,
于是我们考虑拆开。例如:
于是一波地龟就完事了。代码:
ll ksm(ll x,ll y,ll p){
//x^y%p
if(y==0)return 1;
ll z=ksm(x,y/2,p);
z=1ll*z*z%p;
if(y%2)z=z*x%p;
return z;
}
这里我们也可以用相同的思想求大数加法。代码略。
矩阵
定义
一个
运算
加减法:对应数字加减。
矩阵乘数字:所有的数字全部乘。
矩阵乘矩阵
要求第一个矩阵的列数等于第二个矩阵的行数。结果的矩阵行数为第一个的行数,列数为第二个的列数。
算第
写法
加减法及数乘略。
乘法:这里采用结构体和重载来写。
结构体
struct matrix{
ll n,m;
ll a[233][233];
matrix(){//(1)
n=m=0;
memset(a,0,sizeof(a));
}
};
matrix m1,m2,m3;
代码中 (1)
处是构造函数,无需调用,定义结构体就会自动运行。
重载运算符
matrix operator*(matrix &m1,matrix &m2){
matrix m3;
m3.n=m1.n;m3.m=m2.m;
for(int i=0;i<m3.n;i++){
for(int j=0;j<m3.m;j++){
for(int k=0;k<m1.m;k++){
m3.a[i][j]+=m1.a[i][k]*m2.a[k][j];
}
}
}
return m3;
}
但此时我们会发现两个 matrix
如果改动的话,就会寄掉,然后被zhx笑一坤年,因此我们需要加个 const
,于是就成了:
matrix operator*(const matrix &m1,const matrix &m2){
matrix m3;
m3.n=m1.n;m3.m=m2.m;
for(int i=0;i<m3.n;i++){
for(int j=0;j<m3.m;j++){
for(int k=0;k<m1.m;k++){
m3.a[i][j]+=m1.a[i][k]*m2.a[k][j];
}
}
}
return m3;
}
加个输入,就大功告成了。
矩阵快速幂
引入——斐波那契
首先都知道斐波那契数列的递推式:
于是我们准备两个矩阵:
于是……
可这除了更慢,有什么用呢?
我们知道开始的
你想到了……
乘法有快速幂,那矩阵乘法呢?
我们只需要把两个板子塞到一个代码里,变成这样:
struct matrix{
ll n,m;
ll a[2][2];
matrix(){//(1)
n=m=0;
memset(a,0,sizeof(a));
}
};
ll n,p;
matrix m1,m2,tmp,ans;
matrix operator*(const matrix &m1,const matrix &m2){
matrix m3;
m3.n=m1.n;m3.m=m2.m;
for(int i=0;i<m3.n;i++){
for(int j=0;j<m3.m;j++){
for(int k=0;k<m1.m;k++){
m3.a[i][j]+=m1.a[i][k]*m2.a[k][j];
m3.a[i][j]%=p;
}
}
}
return m3;
}
matrix ksm(matrix x,ll y){
if(y==0)return tmp;
matrix z=ksm(x,y/2);
z=z*z;
if(y%2)z=z*x;
return z;
}
然后你就过了一黄一绿。
例题
口胡题:走
用
我们考虑加一个维度
于是
这两个题就是上面说的“一黄一绿”。
我们考虑把这个题变成刚才的口胡题,也就是把带权的图,变成一个边权均为
考虑合并边:
然后就变成了刚才的口胡题。
数论基础
素数的判定
不说了,很简单。重点在为什么可以循环到
常见死法:
for(int i=2;i<=sqrt(x);i++)
死因很明显:每跑一次都要算一次
for(int i=1;i<=strlen(s);i++)
我求
逆元
PAY ATTENTION
只有
互质,才有逆元。——张家瑞
引入——取模
对于
费马小定理
内容:当
证明:
- 两边同除以
得到 。 - 于是原来的
便成了 。一波快速幂就结束了。
欧拉定理
内容:当
证明:
- 将费马小定理进行拓展,现在没有
是质数,只有 互质。
欧拉函数
计算式:
- 如果
有 个质因子: 时 ; 时所有 的倍数全都不互质,即 。- 以此类推,当
时, 。
- 如果
有 个质因子:- 一波容斥,
。
- 一波容斥,
- 从上面的规律中可以推出计算式。
- 如果
线性求逆(I)
- 算出
; - 再求出每一个阶乘的逆元
; - 最后求本数的逆元
即可。
咕咕咕
线性求逆(II)
咕咕咕
Exgcd
求解方程
两边同除以
Miller-Rabin 素性测试
靠脸吃饭的素数判定算法。
如果
只需要找几十个数字,挨个跑一遍判定就可以了。如果跑
int n;
const int NUM=50;
int ksm(int a,int b,int mod){
if(b==1)return a;
int t=ksm(a,b/2);
if(b%2==1)return ((t*t)%mod*a)%mod;
else return (t*t)%mod;
}
bool Miller_Rabin(int n,int a){
int d=n-1,r=0;
while(d%2==0)d/=2,r++;
int t=ksm(a,d,n);
if(t==1)return 1;
for(int i=0;i<r;i++){
if(t==n-1)return 1;
t=(t*t)%n;
}
return 0;
}
bool check(int n){
if(n<2)return 0;
for(int i=1;i<=NUM;i++){
int a=rand()%(n-1)+1;
if(!Miller_Rabin(n,a))return 0;
}
return 1;
}
中国剩余定理
引入——韩信点兵
韩信点兵,三三数之余一,五五数之亦余一,七七数之仍余一,问兵几何?
做法
考虑下面的方程组:
- 我们要解的话,有一种很简单的方法:所有的两两分组解一下;解完之后的再分组解一次,直到就剩一个方程了。于是问题转化为解两个同余方程。
- 正统做法
- 这个做法可以过紫题。
筛法
引入——那次模拟赛
一个夜晚的模拟赛中,你看到了一个题目:
给定一个
,请找出 中的所有质数。
你的脑中浮现了两个思路:
- 暴力:复杂度是
的。 - 想到之前学的 Miller-Rabin,复杂度提升到了
。
你往下滚动着页面,直到看到……
数据规模与约定
对于的数据, 。
对于的数据, 。
对于的数据, 。
对于的数据, 。
“这么厉害的 Miller-Rabin,居然才能得
于是,你打开了百度……
埃氏筛
显然对于每一个质数,其倍数全都不是质数。所以我们考虑开一个 bool
数组
“啊?这都只有
欧拉(线性)筛
埃氏筛的
很显然,尽管是埃氏筛,其还是有可能出现一个数被筛多次的情况,例如
核心代码:
for(int i=2;i<n;i++){
if(not_prime[i]==false){
cnt++;
prime[cnt]=i;
}
for(int j=1;j<=cnt;j++){
int x=prime[j]*i;
if(x>n)break;
not_prime[x]=1;
if(i%prime[j]==0)break;
}
}
你长舒了一口气。这
全剧终。
拓展:积性函数和完全积性函数
定义
积性函数:当
完全积性函数:对于定义域内任意
为什么 是积性函数?
证法一:由计算式即可得到。这里不再赘述。
证法二:
如何求一组连续的 ?
咕咕咕
Baby Step Giant Step(也叫 BSGS、北上广深、拔山盖世、小步大步)算法
引入——小破题
给定
一波暴力:从
由
更加聪明的做法?
考虑把
将第一组每个数字遍历一遍。如果
但这不与上面的一样吗?
发现第二组每一项等于第一组对应项
若第
也就是说,如果第
这样我们就只需要把第一组扫一遍,然后枚举出现
ll a,b,p;
ll ksm(ll a,ll b,ll p){
ll res=1;
a%=p;
while(b){
if(b&1)res=res*a%p;
a=a*a%p,b>>=1;
}
return res;
}
ll solve(ll a,ll b,ll p){
// 求解 a^x % p = b
ll s=sqrt(p),val=1;
set<ll>S;
for(int i=0;i<s;++i){
S.insert(val);
val=val*a%p;
}
for(int i=0;1ll*i*s<=p;++i){
// 查找第 i 组(从 0 开始,这样就查找 b/a^si 即可)
ll inv=ksm(a,i*s,p);
ll tmp=b*ksm(inv,p-2,p)%p;
if(S.count(tmp)){
// inv 就是第 i 行的第一个数
for(int j=i*s;;++j){
// 暴力查找第 i 组中满足条件的数
if(inv==b)return j;
inv=inv*a%p;
}
}
}
return -1;
}
组合数学
加法原理与乘法原理
引入——走路
(1)
从甲地到乙地,有两班飞机和三班火车,有几种走法?
解:
(2)
从甲地到乙地,有两班飞机;从乙地到丙地,有三班火车;一共有几种走法?
解:
为什么 (1)
用加法而 (2)
用乘法捏?
一般地,像 (1)
中同时只能选一个的,使用加法原理;像 (2)
中能同时选两个的,使用乘法原理。
排列与组合
引入——选人排队
(1)从
答:
(2)从
答:
所以
一些东西
定义式
在引入部分都有了。
组合数的性质
。同样不难理解,选 个就相当于选 个不选。 。显然每个东西都有选和不选两种情况,而每个东西之间相互独立。于是就是 了。同理,我们发现:杨辉三角中第 行之和等于 。- 当
时, 。 为偶数时,显然可以直接抵消。 为奇数时,我们考虑杨辉三角:- 杨辉三角的每个数字等于上一行左右两个数字之和。
- 当
为奇数时,第 行所有奇数位置(红色)之和等于第 行所有数字之和;所有偶数位置(绿色)之和也等于上一行的数字之和。于是一减全没了。
组合数递推式
理解:考虑第一个选或者不选。如果选就是在其余
二项式定理
例题
数学题
?- 显然不对,这叫不计顺序?
?- 此时同一个数字被复制多次,但同一个数字被复制到不同的位置时会当成不同的数字,所以还是不对。
- 正解
- 考虑选
个数字并排序。- 如果不能重复,那选出来的
就应满足不等式 ,而我们知道这个不等式的解有 组。 - 而加上能重复的条件之后,不等式就成了
。我们希望将其转化为上面的不等式。 - 考虑一组数字
,其中 。 - 于是我们也可以列出一个不等式:
。 - 然后用与关于
的不等式相同的方法得到这个不等式的解有 组,而这个不等式每一组解就对应了 的不等式的一组解,所以答案就是 。
- 如果不能重复,那选出来的
- 考虑选
编程题
- 求
。- 弱智,直接输出
。
- 弱智,直接输出
- 递推式他不香吗?
为质数。- 可以使用上面提到的线性求逆(I)。
- 给定
,将 拆成 个组合数的和。只要 不同或 不同就叫不同的组合数。- 《电信诈骗》
- 把
拆成 个 和一个 ,于是 。
- 给定
,比较 与 的大小。 的性质:
抽屉原理
引入——还是引入一下吧
一共
题目
- 给定
个数,要求从中选任意多个数,使得它们的和为 的倍数。 。- 史诗级巨坑:《任意多个数》
- 我们加个条件,让选的数必须连续。此时我们可以直接前缀和,得到
个数。 - 按
的值构造抽屉,由于 ,而我们会构造 个抽屉,所以肯定有一个抽屉有至少两个前缀和 ,那么 就是 的倍数。
- 平面上有
个点,用 个 的各边平行于坐标轴的正方形来覆盖所有的点,求最小的 。- 找出这些点的四至点,并画出相应的长方形(红色)。如图:
- 因为只有三个正方形,所以我们必须让某一个正方形覆盖其中的两个点(绿色)。显然放在角上最优。比如这样:
- 此时红色正方形就变成了剩余点的四至点。同理,我们再用另一个正方形(蓝色)再次覆盖两个四至点:
- 最后用一个正方形(橙色)覆盖最后剩的点。
- 当然上图显然不是最优的。我们把所有可能的情况全部枚举,计算最小值即可。
· 记一次 nginx 配置不当引发的499
· .NET 开源分布式锁 DistributedLock
· 聊聊「低代码」的实践之路
· 超长溢出头部省略打点,坑这么大,技巧这么多?
· 记一次 Windows10 内存压缩模块 崩溃分析
· 基于ChatGPT用AI实现自然对话
· 记一次nginx配置不当引发的499与failover 机制失效
· 大话AI绘画技术原理与算法优化
· 响应式的 switchboard:让又大又慢的Vue/AIpine 页面爆快
· Netty之数据解码