OI模板-数学
注意,本文不同于 OI数学总结-数论 或其他总结类文章
本文倾向于代码实现,只是因为数论的内容与代码联系紧密,并且数论内容较多,因此显得较为相似。
最大公约数 gcd
最大公约数指能够整除多个不全为零的整数的最大正整数
常用性质: $a\times b = \gcd(a,b) \times \mathrm{lcm}(a,b)$
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
} // 注意这份代码对于(+,-)的情况会求出负数
扩展欧几里德算法(Exgcd)
板子题: P5656 【模板】二元一次不定方程 (exgcd)
若 $b \ne 0$ ,扩展欧几里得算法求出的可行解必有 $|x| \le b ,|y| \le a$ 。
int exgcd(int a,int b,int &x,int &y)
{
int d=a;
if(!b)x=1,y=0;
else d=exgcd(b,a%b,y,x),y-=a/b*x;
return d;
}
void solve()
{
int a,b,c,x,y;
read(a);read(b);read(c);
int d=exgcd(a,b,x,y);
if(c%d!=0){puts("-1");return;} // 无解
x*=c/d;y*=c/d;
int p=b/d,q=a/d,k;
if(x<0)k=ceil((1.0-x)/p),x+=p*k,y-=q*k; // 将x提高到最小正整数
else k=(x-1)/p,x-=p*k,y+=q*k; // 将x降低到最小正整数
if(y>0) // 存在x>0,y>0的解(正整数)
{
write((y-1)/q+1); pc(' '); // 将y减到1的方案数即为解的个数
write(x); pc(' '); // 当前的x为最小正整数
write((y-1)%q+1); pc(' '); // 将y取到最小正整数
write(x+(y-1)/q*p); pc(' '); // 将x提升到最大
write(y); pc(' '); // 特解即为y的最大值
}else // 仅存在整数解
{
write(x); pc(' '); // 当前的x
write(y+q*(int)ceil((1.0-y)/q));pc(' '); // 将y提高到正整数
}
pc('\n');
}
逆元
如果一个线性同余方程组 $ax \equiv 1 \pmod{b}$ ,则 $x$ 称为 $a \bmod b$ 的逆元,记作 $a^{-1}$
模意义下的乘法逆元不唯一。如果 $b$ 不为素数,可能不存在逆元。
模板题:P3811 【模板】乘法逆元
求解逆元有多重方法,我们一个个来讲。
第一种方法:$O(n)$ 预处理 ,$O(1)$ 查询
求的是 $1,2,\cdots ,n$ 的模 $p$ 意义下的乘法逆元,$p$ 一般为质数。
为什么说一般呢?因为「 $p$ 为合数」会导致有些时候不存在逆元。
如果正好存在逆元也是可以继续做的。
原理:
注意空间复杂度为 $O(n)$
inv[1] = 1;
for(int i = 2; i <= n; i++)
inv[i] = (p - p / i) * inv[p % i] % p;
第二种方法:费马小定理 $O(\log p)$ 查询 $x$ 的逆元
原理:$x \equiv a^{p-2}\pmod{p}$ (根据费马小定理),$p$ 需要是质数
cout << qpow(a,p-2) << '\n'; // 快速幂
第三种方法:$\text{Exgcd}~O(\log p)$ 查询 $x^{-1}$
求的是 $a$ 在模 $p$ 意义下的逆元,要求模数 $p$ 与 $a$ 互质。
int exgcd(int a,int b,int &x,int &y)
{
int d = a;
if(!b) { x = 1,y = 0; }
else { d = exgcd(b, a % b, y, x), y -= a / b * x; }
return d;
}
int solve(int a,int p)
{
int x,y;
exgcd(a,p,x,y);
return (x = (x % p + p) % p);
}
第四种方法(特殊):$\mathcal{O}(n)$ 预处理阶乘的逆元
因为 $n!=(n-1)! \times n$ ,所以 $((n-1)!)^{-1}=(n!)^{-1} \times n$ 。模数 $p$ 为质数。
for(int i=1; i<=n; i++) fac[i] = fac[i-1] * i % p;
invf[n] = qpow(fac[n], p-2); // 费马小定理
for(int i=n-1; i>=1; i--) invf[i] = invf[i+1] * (i+1) % p;
请注意,如果 $p \le n$ ,这种方法就不可行了,因为 $n! \bmod p =0$ ,会出现不存在逆元的情况
这种时候一般采用上文线性求逆元的方法,只要求 $1\sim p-1$ 的就行了,即
// luogu P2675
for(int i=1; i<=n; i++) fac[i] = fac[i-1] * i % p;
for(int i=2; i<p; i++) inv[i] = (p-p/i) * inv[p%i] % p; // 注意是1 ~ p-1
欧拉函数 $\varphi$
欧拉函数 $\varphi(n)$ 表示小于等于 $n$ 和 $n$ 互质的数的个数。
设 $n=\prod_{i=1}^{s}p_i^{k_i}$
常用性质:$\varphi(1) = 1,~\varphi(p) = p-1,~p\in\mathbb{P}$
模板题:poj2407 。套路地,欧拉函数也有多种求解方法。
第一种方法:$O(\sqrt{n})$ 求 $\varphi(n)$
这种做法就是根据欧拉函数的定义,模拟计算。
int Euler_phi(int n)
{
int ans=n;
for(int i=2; i<=n/i; i++)
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n;
while(cin>>n&&n!=0)
cout << Euler_phi(n) << endl;
return 0;
}
第二种方法:线性筛 $O(n)$ 求解
可能是模板题:poj2478 (这个题要改成 $\varphi$ 的前缀和)
int phi[N],prime[N],pcnt;
bool ck[N];
void Euler()
{
ck[1]=1;
phi[1]=1;
for(int i=2; i<=N-5; i++)
{
if(!ck[i])
{
prime[++pcnt]=i;
phi[i]=i-1;
}
for(int j=1; j<=pcnt&&i*prime[j]<=N-5; j++)
{
int pos=i*prime[j];
ck[pos]=1;
if(i%prime[j])
{
phi[pos]=phi[i]*phi[prime[j]];
}
else
{
phi[pos]=phi[i]*prime[j];
break;
}
}
}
}
扩展欧拉定理
例题:
求
$1\le a,m\le 10^{12},1\le b\le 10^{20000000}$(这个是加强版的,朴素版只要int就行)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define gc() getchar()
int a,m,b,flag;
int Euler_phi(int n)
{
int ans=n;
for(int i=2; i<=n/i; i++)
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
int qpow(int a,int b,int p)
{
int ans=1,base=a%p;
while(b)
{
if(b&1)ans=(__int128)ans*base%p;
base=(__int128)base*base%p;
b>>=1;
}
return ans;
}
signed main()
{
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
scanf("%lld%lld",&a,&m);
a%=m;
int phi=Euler_phi(m);
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))
{
b=(b<<1)+(b<<3)+(ch^48);
if(b>=phi)flag=1,b%=phi;ch=gc();
}
if(b>=phi)flag=1,b%=phi;
if(flag)b+=phi;
printf("%lld\n",qpow(a,b,m));
return 0;
}
Lucas定理
对于质数 $p$ ,有
$\binom{\left\lfloor{n/p}\right\rfloor}{\left\lfloor{m/p}\right\rfloor}$ 采用递归处理,后面的直接 $O(p)$ 预处理组合数
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e5+15)
int Q,n,m,p;
int fac[N];
int qpow(int a,int b,int p)
{
int ans=1,base=a;
while(b)
{
if(b&1)ans=ans*base%p;
base=base*base%p;
b>>=1;
}
return ans;
}
// n 选 m
int C(int n,int m,int p)
{
if(n<m)return 0;
return fac[n]*qpow(fac[m],p-2,p)%p*qpow(fac[n-m],p-2,p)%p;
}
int lucas(int n,int m,int p)
{
if(!m)return 1;
return lucas(n/p,m/p,p)*C(n%p,m%p,p)%p;
}
signed main()
{
cin >> Q;
while(Q--)
{
cin >> n >> m >> p;
fac[1]=fac[0]=1;
for(int i=2; i<=n+m; i++)
fac[i]=fac[i-1]*i%p;
cout << lucas(n+m,m,p) << '\n'; // C(n+m,n)
}
return 0;
}
中国剩余定理
参考 中国剩余定理 & 扩展
中国剩余定理(CRT)可求解如下形式的一元线性同余方程组
其中 $p_1,p_2,\cdots,p_k$ 两两互质。
板子题:P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪
时间复杂度 $\mathcal{O}(n \log V)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(15))
int a[N],p[N];
void exgcd(int a, int b, int &x, int &y)
{
if(!b) { x = 1, y = 0; }
else exgcd(b, a % b, y, x), y -= a / b * x;
}
int solve(int n, int P)
{
int res = 0;
for(int i = 1; i <= n; i++)
{
int m = P / p[i], x, y;
exgcd(m, p[i], x, y); // m * x % p[i] = 1;
res = (res + a[i] * m % P * x % P) % P;
}
return (res % P + P) % P;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n, P = 1; cin >> n;
for(int i = 1; i <= n; i++) {
cin >> p[i] >> a[i]; P = P * p[i];
}
cout << solve(n, P) << '\n';
return 0;
}
扩展中国剩余定理
ExCRT 用于解决 $p_i$ 不一定互质的情况
时间复杂度 $\mathcal{O}(n \log V)$
代码:
// 2024年02月09日 03:38:48
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))
int exgcd(int a, int b, int &x, int &y)
{
int d = a;
if(!b) { x = 1, y = 0; }
else { d = exgcd(b, a % b, y, x), y -= a / b * x;}
return d;
}
int a[N], p[N];
int ExCRT(int n)
{
int u, v, p1 = p[1], res = a[1];
for(int i = 2; i <= n; i++)
{
int p2 = p[i], c = (a[i] - res % p2 + p2) % p2;
int g = exgcd(p1, p2, u, v); assert(c % g == 0);
int l = p2 / g; u = (__int128_t) u * (c / g) % l;
res += u * p1; p1 *= l; res = (res % p1 + p1) % p1;
}
return (res % p1 + p1) % p1;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n; cin >> n;
for(int i = 1; i <= n; i++) cin >> p[i] >> a[i];
cout << ExCRT(n) << '\n';
return 0;
}
莫比乌斯函数 $\mu$
$\mu$ 为莫比乌斯函数,定义为
一般线性筛 $\mathcal{O}(n)$ 求 $\mu$ ,用于莫比乌斯反演,参考 OI数学总结-数论
// 2023年07月04日 20:43:15
void Mobius(int n = N - 10)
{
mu[1] = 1;
for(int i = 2; i <= n; i++)
{
if(!ck[i]) { prime[++pcnt] = i; mu[i] = -1; }
for(int j = 1; j <= pcnt && i * prime[j] <= n; j++)
{
int pos = i * prime[j]; ck[pos] = true;
if(i % prime[j]) { mu[pos] = -mu[i]; } else { mu[pos] = 0; break; }
}
}
}
排列数
一般排列数我们都利用 $A^m_n = m! \times C^m_n$ 计算,下面我们讲一个特殊的计算方法。
排列 $A_n^m$ 直接算可以 $O(n)$
例如求解 $A_{n-m+1}^{m} \bmod p$
int res=1;
for(int i=n-m+1; i>=n-2*m+2; i--)
res=res*i%p;
cout << res << '\n'
组合数
从 $n$ 个不同元素中取 $k(k \le n)$ 个不同元素的方案数
高中一般记作 $\mathrm{C}_n^{k}$ ,不是很推荐使用。
特别地,规定当 $k > n$ 时, $\mathrm{A}_n^{k} = \mathrm{C}_n^{k}=0$
$O(n^2)$ 递推
for(int i=0; i<=1000; i++)
{
C[i][0]=1;
for(int j=1; j<=i; j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
}
$O(p)$ Lucas定理
详见 [OI数学总结-数论]
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e5+15)
int Q,n,m,p;
int fac[N];
int qpow(int a,int b,int p)
{
int ans=1,base=a;
while(b)
{
if(b&1)ans=ans*base%p;
base=base*base%p;
b>>=1;
}
return ans;
}
// n 选 m
int C(int n,int m,int p)
{
if(n<m)return 0;
return fac[n]*qpow(fac[m],p-2,p)%p*qpow(fac[n-m],p-2,p)%p;
}
int lucas(int n,int m,int p)
{
if(!m)return 1;
return lucas(n/p,m/p,p)*C(n%p,m%p,p)%p;
}
signed main()
{
cin >> Q;
while(Q--)
{
cin >> n >> m >> p;
fac[1]=fac[0]=1;
for(int i=2; i<=n+m; i++)
fac[i]=fac[i-1]*i%p;
cout << lucas(n+m,m,p) << '\n'; // C(n+m,n)
}
return 0;
}
快速幂
时间复杂度 $O(\log n)$
空间复杂度 $O(1)$
// 2024年05月28日 10:48:24
int qpow(int a, int b)
{
int r = 1;
for(; b; b >>= 1, a = a * a % mod)
if(b & 1) r = r * a % mod;
return r;
}
矩阵快速幂
注意点:
- $O(n^3\log M)$
- 初始状态
- 临时变量不可开过大
新的板子(upd.20220730) P1962 斐波那契数列 ,此时 $n=2,~M$ 则为代码中的 $n$ 。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
typedef vector< vector<int> > mat;
#define N (int)()
const int mod=1e9+7;
void add(int &x,int y){x+=y; if(x>=mod)x-=mod;}
// (n*m) x (m*p) = (n*p)
mat mul(mat &A,mat &B)
{
int n=A.size(),m=A[0].size(),p=B[0].size();
mat C(n,vector<int>(p));
for(int k=0; k<m; k++)
for(int i=0; i<n; i++)
for(int j=0; j<p; j++)
add(C[i][j],A[i][k]*B[k][j]%mod);
return C;
}
mat qpow(mat A,int k)
{
int n=max(A.size(),A[0].size());
mat B(n,vector<int>(n));
for(int i=0; i<n; i++) B[i][i]=1;
while(k)
{
if(k&1) B=mul(B,A);
A=mul(A,A); k>>=1;
}
return B;
}
void print(mat &A)
{
int n=A.size(),m=A[0].size();
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
cout << A[i][j] << " \n"[j==m-1];
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n; cin >> n;
if(n<=2) cout << "1";
else
{
mat A(2,vector<int>(2)),B(2,vector<int>(1));
A[0][0]=A[0][1]=A[1][0]=1;
B[0][0]=B[1][0]=1;
A=qpow(A,n-2); A=mul(A,B);
cout << A[0][0] << '\n';
}
return 0;
}
旧的板子
const int p=2009;
int n,t;
struct mat
{
int n,g[205][205];
void clear()
{
memset(g,0,sizeof(g));
}
mat operator*(const mat &o)const
{
static mat tmp;
tmp.n=n;tmp.clear();
for(int i=1; i<=n; i++)
for(int k=1; k<=n; k++)
{
int r=g[i][k]%p;
for(int j=1; j<=n; j++)
tmp.g[i][j]=(tmp.g[i][j]+r*o.g[k][j]%p)%p;
}
return tmp;
}
}M;
mat qpow(mat a,int b)
{
static mat ans,base=a;
ans.n=a.n;ans.clear();
for(int i=1; i<=ans.n; i++)
ans.g[i][i]=1;
while(b)
{
if(b&1)ans=ans*base;
base=base*base;
b>>=1;
}
return ans;
}
判断素数
判断素数 朴素版
时间复杂度 $O(Q\sqrt{n})$
#include <bits/stdc++.h>
using namespace std;
#define int long long
int x;
bool ck(int x)
{
if(x<2)return 0;
for(int i=2; i<=x/i; i++)
{
if(x%i==0)
return 0;
}
return 1;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
while(cin >> x)
cout << (ck(x)?'Y':'N') << endl;
return 0;
}
MillerRabin
$O(Q\log a_i)$
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define test_time 10
mt19937 rd(time(0));
int mul(int a,int b,int p)
{
int res=(__int128)a*b%p;
return res;
}
int qpow(int a,int b,int p)
{
int ans=1,base=a;
while(b)
{
if(b&1)ans=mul(ans,base,p);
base=mul(base,base,p);
b>>=1;
}
return ans;
}
bool MillerRabin(int n)
{
if(n<3||n%2==0)return n==2;
int a=n-1,b=0;
while(a%2==0)a>>=1,++b;
for(int i=1,j; i<=test_time; i++)
{
int x=rd()%(n-2)+2;
int v=qpow(x,a,n);
if(v==1)continue;
for(j=0; j<b; j++)
{
if(v==n-1)break;
v=mul(v,v,n);
}
if(j>=b)return 0;
}
return 1;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int x;
while(cin >> x)
cout << (MillerRabin(x)?'Y':'N') << endl;
return 0;
}
线性筛
时间复杂度 近似$O(n)$
空间复杂度 $O(n)$
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define gc() getchar()
#define pc(a) putchar(a)
#define INF 0x3f3f3f3f3f3f3f3f
#define MAXN (int)(1e8+5)
#define MAXM (int)(2e6+5)
template<typename T>inline void read(T &k)
{char ch=gc();T x=0,f=1;while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}k=x*f;}
template<typename T>inline void write(T k)
{if(k<0){k=-k;pc('-');}if(k>9)write(k/10);pc(k%10+'0');}
int n,p;
bool ck[MAXN];
int a[MAXM],tot;
void prime()
{
for(int i=2; i<=n+2; i++)
{
if(!ck[i])a[++tot]=i;
for(int j=1; j<=tot&&a[j]*i<=n+2; j++)
{
ck[a[j]*i]=1;
if(i%a[j]==0)break;
}
}
}
signed main()
{
read(n);read(p);
prime();
for(int i=1,k; i<=p; i++)
{
read(k);
write(a[k]);pc('\n');
}
return 0;
}
求欧拉函数
- 暴力 $O(Q\sqrt{n})$ poj2407
int Euler_phi(int n)
{
int ans=n;
for(int i=2; i<=n/i; i++)
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n;
while(cin>>n&&n!=0)
cout << Euler_phi(n) << endl;
return 0;
}
线性筛求欧拉函数 $O(n)$
poj2478 (这个题要改成 $\varphi$ 的前缀和)
int phi[N],prime[N],pcnt;
bool ck[N];
void Euler()
{
ck[1]=1;
phi[1]=1;
for(int i=2; i<=N-5; i++)
{
if(!ck[i])
{
prime[++pcnt]=i;
phi[i]=i-1;
}
for(int j=1; j<=pcnt&&i*prime[j]<=N-5; j++)
{
int pos=i*prime[j];
ck[pos]=1;
if(i%prime[j])
{
phi[pos]=phi[i]*phi[prime[j]];
}
else
{
phi[pos]=phi[i]*prime[j];
break;
}
}
}
}
杜教筛
当 $m = n^{\frac{2}{3}}$ 时,$T(n) = \mathcal{O}(n^{\frac{2}{3}})$ 。
// 2024年06月26日 00:32:57
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(5e6 + 15))
char ck[N]; int pcnt, p[N]; int phi[N], mu[N];
unordered_map<int, int> sum_phi, sum_mu;
void init(const int _ = N - 5)
{
phi[1] = mu[1] = 1;
rep(i, 2, _)
{
if(!ck[i]) { p[++pcnt] = i; phi[i] = i - 1; mu[i] = -1; }
for(int j = 1; j <= pcnt && i * p[j] <= _; j++)
{
const int pos = i * p[j]; ck[pos] = true;
if(i % p[j] == 0) { phi[pos] = phi[i] * p[j]; break; }
else { phi[pos] = phi[i] * phi[p[j]]; mu[pos] = -mu[i]; }
}
}
rep(i, 1, _) { phi[i] += phi[i - 1]; mu[i] += mu[i - 1]; }
}
int get_sum_phi(int n)
{
if(n <= N - 5) return phi[n];
if(sum_phi[n]) return sum_phi[n];
int res = 1ll * n * (n + 1) / 2;
for(int l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= 1ll * (r - l + 1) * get_sum_phi(n / l);
}
return sum_phi[n] = res;
}
int get_sum_mu(int n)
{
if(n <= N - 5) return mu[n];
if(sum_mu[n]) return sum_mu[n];
int res = 1ll;
for(int l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= 1ll * (r - l + 1) * get_sum_mu(n / l);
}
return sum_mu[n] = res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int qwq; cin >> qwq; init();
while(qwq--) {
static int x; cin >> x;
cout << get_sum_phi(x) << ' ' << get_sum_mu(x) << '\n';
}
return 0;
}
BSGS
传送门:洛谷P3846 [TJOI2007] 可爱的质数/【模板】BSGS 题解
求解 $a^x\equiv b\pmod{p}$ ,其中 $a,b,p$ 均给出, $p$ 为质数,$a,b < p$ 。
// 2024年02月21日 17:33:40
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)())
int P;
int qpow(int a, int b)
{
int r = 1;
while(b)
{
if(b & 1) r = r * a % P;
b >>= 1; a = a * a % P;
}
return r;
}
map<int,int> mp;
int BSGS(int a, int b)
{
int t = sqrt(P) + 1;
for(int i = 0; i < t; i++) mp[b * qpow(a, i) % P] = i;
a = qpow(a, t);
if(!a) return (b == 0 ? 1 : -1);
for(int i = 1; i <= t; i++)
{
int val = qpow(a, i);
int j = mp.find(val) == mp.end() ? -1 : mp[val];
if(j >= 0 && i * t - j >= 0) return i * t - j;
}
return -1;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int a,b; cin >> P >> a >> b;
int res = BSGS(a,b);
if(res == -1) cout << "no solution\n";
else cout << res << '\n';
return 0;
}
二次剩余
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
int Q,n,p;
int i2;
struct num
{
int r,i;
};
num operator*(num a,num b)
{
return {((a.r*b.r%p+i2*a.i%p*b.i%p)+p)%p,
((a.i*b.r%p+a.r*b.i%p)+p)%p};
}
bool operator==(num a,num b)
{
return a.r==b.r&&a.i==b.i;
}
num qpow(num a,int b)
{
num ans={1,0},base=a;
while(b)
{
if(b&1)ans=ans*base;
base=base*base;
b>>=1;
}
return ans;
}
bool ck(int x)
{
return qpow({x,0},(p-1)>>1)==(num){1,0};
}
mt19937 rd(time(0));
void solve(int &x1,int &x2)
{
int a=rd()%p;
while(!a||ck((a*a+p-n)%p))
a=rd()%p;
i2=(a*a+p-n)%p;
x1=qpow((num){a,1},(p+1)>>1).r;
x2=p-x1;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int x1,x2;
cin >> Q;
while(Q--)
{
cin >> n >> p;
if(qpow((num){n,0},(p-1)>>1).r==p-1)
cout << "Hola!" << endl;
else if(!n)cout << 0 << endl;
else
{
solve(x1,x2);
if(x1>x2)swap(x1,x2);
if(x1==x2)cout << x1 << endl;
else cout << x1 << " " << x2 << endl;
}
}
return 0;
}
数论分块
具体参考 数论分块 学习笔记
例题2 P2424 约数和
例题1代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin >> n >> k;int res=n*k;
for(int l=1,r; l<=min(k,n); l=r+1)
{
r=min(k/(k/l),n);
res-=(k/l)*(r-l+1)*(l+r)/2;
}
cout << res << endl;
return 0;
}
康托展开
时间复杂度 $O(n\log n)$
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1e6+15)
#define gc() readchar()
#define pc(a) putchar(a)
#define SIZ (int)(1e6+15)
char buf1[SIZ],*p1=buf1,*p2=buf1;
char readchar()
{
if(p1==p2)p1=buf1,p2=buf1+fread(buf1,1,SIZ,stdin);
return p1==p2?EOF:*p1++;
}
template<typename T>void read(T &k)
{
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();};
k=x*f;
}
template<typename T>void write(T k)
{
if(k<0){k=-k;pc('-');}
static T stk[66];T top=0;
do{stk[top++]=k%10,k/=10;}while(k);
while(top){pc(stk[--top]+'0');}
}
const int mod = 998244353;
int n,a[N],fac[N],tree[N];
#define lowbit(x) (x&-x);
void add(int x,int v)
{
while(x&&x<=n)
{
tree[x]+=v;
x+=lowbit(x);
}
}
int sum(int x)
{
int res=0;
while(x>=1)
{
res+=tree[x];
x-=lowbit(x);
}
return res;
}
signed main()
{
read(n);
fac[0]=1;
for(int i=1; i<=n; i++)
fac[i]=fac[i-1]*i%mod,add(i,1);
int ans=1;
for(int i=1; i<=n; i++)
{
read(a[i]);
ans+=sum(a[i]-1)*fac[n-i]%mod;
ans%=mod;add(a[i],-1);
}
write(ans);pc('\n');
return 0;
}
逆康托展开
理论复杂度是 $O(n\log n)$ 的,但是实际上用暴力就行了(n!那么大
这个纯属瞎写的。根本用不到,但是复杂度确实是对的
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1e3+15)
int n,a[N],fac[N],Q;
namespace BT
{
struct node
{
int ch[2],num,cnt,s,sz,sd;
}t[N];
#define ls(x) t[x].ch[0]
#define rs(x) t[x].ch[1]
double alpha=0.7;
int tot,rt;
int tmp[N];
int New(int x)
{
t[++tot]={0,0,x,1,1,1,1};
return tot;
}
void push_up(int at)
{
t[at].s=t[ls(at)].s+t[rs(at)].s+1;
t[at].sz=t[ls(at)].sz+t[rs(at)].sz+t[at].cnt;
t[at].sd=t[ls(at)].sd+t[rs(at)].sd+(t[at].cnt!=0);
}
bool CanR(int at)
{
double x=t[at].s*alpha;
return (t[at].cnt)&&(x<=(double)max(t[ls(at)].s,t[rs(at)].s)||
(double)t[at].sd<=x);
}
void CanR_flatten(int &idx,int at)
{
if(!at)return;
CanR_flatten(idx,ls(at));
if(t[at].cnt)tmp[++idx]=at;
CanR_flatten(idx,rs(at));
}
int CanR_build(int l,int r)
{
if(l>=r)return 0;
int mid=(l+r)>>1;
int &at=tmp[mid];
ls(at)=CanR_build(l,mid);
rs(at)=CanR_build(mid+1,r);
push_up(at);
return at;
}
void rebuild(int &at)
{
int idx=0;
CanR_flatten(idx,at);
at=CanR_build(1,idx+1);
}
void insert(int x,int &at)
{
if(!at){at=New(x);return;}
if(t[at].num==x)++t[at].cnt;
else if(x<t[at].num)insert(x,ls(at));
else insert(x,rs(at));
push_up(at);
if(CanR(at))rebuild(at);
}
void remove(int x,int &at)
{
if(!at)return;
if(t[at].num==x)
{
if(t[at].cnt)--t[at].cnt;
}else if(x<t[at].num)remove(x,ls(at));
else remove(x,rs(at));
push_up(at);
if(CanR(at))rebuild(at);
}
int getval(int x,int at)
{
if(!at)return INF;
if(x<=t[ls(at)].sz)return getval(x,ls(at));
if(x<=t[ls(at)].sz+t[at].cnt)return t[at].num;
else return getval(x-t[ls(at)].sz-t[at].cnt,rs(at));
}
int uprbd(int x,int at)
{
if(!at)return 1;
if(x==t[at].num&&t[at].cnt)return t[ls(at)].sz+t[at].cnt+1;
else if(x<t[at].num)return uprbd(x,ls(at));
else return uprbd(x,rs(at))+t[ls(at)].sz+t[at].cnt;
}
int uprbd_gt(int x,int at)
{
if(!at)return 0;
if(x==t[at].num&&t[at].cnt)return t[ls(at)].sz;
else if(x<t[at].num)return uprbd_gt(x,ls(at));
else return uprbd_gt(x,rs(at))+t[ls(at)].sz+t[at].cnt;
}
int getpre(int x,int at){return getval(uprbd_gt(x,at),at);}
int getnext(int x,int at){return getval(uprbd(x,at),at);}
}
namespace BIT
{
int tree[N];
#define lowbit(x) (x&-x)
void add(int x,int v)
{
while(x&&x<=n)
{
tree[x]+=v;
x+=lowbit(x);
}
}
int sum(int x)
{
int res=0;
while(x>=1)
{
res+=tree[x];
x-=lowbit(x);
}
return res;
}
}
signed main()
{
using namespace BIT;
using namespace BT;
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin >> n >> Q;fac[0]=1;
for(int i=1; i<=n; i++)
fac[i]=fac[i-1]*i,add(i,1);
while(Q--)
{
char op;
cin >> op;
if(op=='P')
{
int k,r;cin >> k;
tot=rt=0;--k;
for(int i=1; i<=n; i++)
insert(i,rt);
for(int i=1; i<=n; i++)
{
r=k%fac[n-i];k/=fac[n-i];
int p=getval(k+1,rt);
// cout << p << endl;
cout << p << " \n"[i==n];
remove(p,rt);k=r;
}
}else
{
int ans=1;
memset(tree,0,(n+1)*sizeof(int));
for(int i=1; i<=n; i++) add(i,1);
for(int i=1,x; i<=n; i++)
{
cin >> x;
ans+=sum(x-1)*fac[n-i];
add(x,-1);
}
cout << ans << endl;
}
}
return 0;
}
高斯消元
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(150))
const double eps = 1e-10;
int dcmp(int x)
{
if(abs(x) <= eps) return 0;
return x > eps ? 1 : -1;
}
int n; double a[N][N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cout << fixed << setprecision(2);
cin >> n;
for(int i=1; i<=n; i++)
for(int j=1; j<=n+1; j++)
cin >> a[i][j];
for(int i=1; i<=n; i++) // col
{
int p = i;
for(int j=i+1; j<=n; j++)
if(abs(a[j][i]) > abs(a[p][i])) p=j; // row
for(int j=1; j<=n+1; j++)
swap(a[i][j],a[p][j]); // swap p to i for proc
if(!dcmp(a[i][i])) return cout << "No Solution\n",0;
for(int j=1; j<=n; j++)
if(j != i)
{
double tmp = a[j][i] / a[i][i];
for(int k=1; k<=n+1; k++)
a[j][k] -= a[i][k] * tmp;
}
}
for(int i=1; i<=n; i++)
cout << a[i][n+1] / a[i][i] << '\n';
return 0;
}