这是我参与「第四届青训营 」笔记创作活动的第15天
作为一名前端人员,不仅要掌握庞大的前端知识体系,有时候在性能优化的时候掌握一些技巧,比如数论的知识,可以有效帮助开发者增强程序的性能与效率。
容斥原理
概念
要把1-n中2、3、5的倍数全部筛掉,那么先筛2的倍数,再筛3的倍数,再筛5的倍数,这样的话6、10、15的倍数就被筛了两遍,30的倍数就被筛了三遍。因此,只要减去6、10、15的倍数,但是这样一减,30的倍数又被减了3遍,也就是说,30的倍数没有被加过,所以再加上就可以了。
容斥原理可以用来求1-n中的所有质数。
实现
先分解质因子:
for (int i = 2; i * i <= k; i++) {
if (k % i == 0) {
p[++tail] = i; //p就是储存质因子的数组
while (k % i == 0) k /= i; //把k中所有i的质因子全部除去
}
}
if (k > 1) p[++tail] = k; //最后如果大于一,则最后一个数一定是质因子,这一步可能有一点难理解,可以多想想
再实现容斥:
long long fun(long long x){
long long res=0; //记录1-x中与k不互质的数量
for(int i=1;i<(1<<tail);i++){ //这里的1<<tail是指2的tail次方,表示tail个质因子有多少种组合情况
long long cur=1,cnt=0; //cur表示在当前选中的质因子中的乘积,cnt表示当前选中的数量是奇数还是偶数
for(int j=0;j<tail;j++){ //这个循环是枚举tail的二进制形式
if((i>>j)&1){ //这个是判断i的第j位是不是1,如果是则表示选中第j个数
cnt++; //表示选中了几个数,每选中一个就加一
cur*=p[j+1]; //选中第j个数就用cur乘以第j个质因子数,注意质因子数组是从1开始的,所以要加一
}
}
if(cnt&1) res+=x/cur; //如果cnt是偶数就相加
else res-=x/cur; //奇数就相减
}
return x-res; //res储存的是1-x中与K不互质的数量,所以要用x-res得到互质的数量
}
康托展开
定义 :给定自然数排列,求出由小到大中所有排列的该排列序号
以下是O(n^2)做法,可用树状数组存tmp,将时间复杂度降低到O(nlogn)
void jiecheng(int x)
{
f[0]= f[1] =1;
for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i;
}
int kangtuo()
{
int ans =1;
int len = str.length();
for(int i=0;i<len;i++)
{
int tmp =0;
for(int j=i+1;j<len;j++)
{
if(str[i]>str[j]) tmp++;
}
ans+=tmp*f[len-i+1];
}
return ans;
}
逆展开
定义 :给定一个序号,求出对应的自然数排列
void jie_cheng(int n)
{
f[0] = f[1] = 1; // 0的阶乘为1
for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i;
}
vector<char> vec; //存需要排列的字符
void rev_kangtuo(int k) //输出序号为 k 的字符序列
{
int n = vec.size(), len = 0;
string ans = "";
k--; // 算的时候是按 12345 是第0位
for(int i = 1; i <= n; i++){
int t = k / f[n - i]; // 第 i 位需要 第 t + 1 大的数
k %= f[n - i]; //剩下的几位需要提供的排列数
ans += vec[t] ; // vec[t] 就是第 t + 1 大的数
vec.erase(vec.begin() + t);
//用过就删了,不用vector用暴力也可以,就是说枚举,然后一个一个的比较大小,并记录有几个没用过的字符且字典序比它小
}
cout << ans << '\n';
}
乘法逆元
定义
若a与p互质 a * b = 1 (mod p) --> a与b的乘积在模p时恒等于1,则b是a在模p情况下的逆元
可以根据乘法逆元,将 (a / b) % c 转化为 (a * x) % c ,将除法转化为乘法。
解法
费马小定理
假如p是质数,且gcd(a,p)=1,那么 a^ {p-1}≡1(mod p) 。
根据费马小定理,a 的逆元即为 a^{p-2}
需要结合快速幂
ll fpm(ll x, ll power, ll mod) {
x %= mod;
ll ans = 1;
for (; power; power >>= 1, (x *= x) %= mod)
if(power & 1) (ans *= x) %= mod;
return ans;
}
int main() {
ll x = fpm(a, p - 2, p); //x为a在mod p意义下的逆元
}
扩展欧几里得定理
必存在 x y ,满足等式 ax + by = gcd(a,b) 因此,若gcd(a,b)=1,则可以求乘法逆元
可递归得出结论:x = y' ,y = x'- a/b * y'
void exgcd(ll a, ll b, ll &x, ll &y)
{
if (!b) //递归到特解开始回溯
{
x = 1;
y = 0;
return;
}
exgcd(b, a % b, x, y); //求得x2,y2
ll temp = x;
x = y; //回溯,利用x2递推x1
y = temp - (a / b) * y; //回溯,利用y2递推y1
}
int main()
{
ll a, b, x, y;
cin >> a >> b;
exgcd(a, b, x, y);
cout << x << ' ' << y << endl;
return 0;
}
递推公式
从一到n的逆元:
这个式子必须从1开始递推