主要都根据OIWIKI路线学习的
字符串
数学
数论
反素数
#include <cstdio>
#include <iostream>
int p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
unsigned long long n;
unsigned long long ans,
ans_num; // ans 为 n 以内的最大反素数(会持续更新),ans_sum 为
// ans的因子数。
// depth: 当前在枚举第几个素数
// temp: 当前因子数量为 num的时候的数值
// num: 当前因子数
// up:上一个素数的幂,这次应该小于等于这个幂次嘛
void dfs(int depth, unsigned long long temp, unsigned long long num, int up) {
if (depth >= 16 || temp > n) return;
if (num > ans_num) { // 更新答案
ans = temp;
ans_num = num;
}
if (num == ans_num && ans > temp) ans = temp; // 更新答案
for (int i = 1; i <= up; i++) {
if (temp * p[depth] > n)
break; // 剪枝:如果加一个这个乘数的结果比ans要大,则必不是最佳方案
dfs(depth + 1, temp *= p[depth], num * (i + 1),
i); // 取一个该乘数,进行对下一个乘数的搜索
}
return;
}
int main() {
while (scanf("%llu", &n) != EOF) {
ans_num = 0;
dfs(0, 1, 1, 60);
printf("%llu\n", ans);
}
return 0;
}
欧拉函数
// C++ Version
int euler_phi(int n) {
int ans = n;
for (int i = 2; i * i <= n; 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;
}
欧拉定理
与欧拉函数紧密相关的一个定理就是欧拉定理。其描述如下:
若
gcd
(
a
,
m
)
=
1
,
则
a
φ
(
m
)
≡
1
(
m
o
d
m
)
\gcd (a,m) = 1,则{a^{\varphi (m)}} \equiv 1 (\bmod m)
gcd(a,m)=1,则aφ(m)≡1(modm)
筛法
素数筛法 O(n)
// C++ Version
void init() {
for (int i = 2; i < MAXN; ++i) {
if (!vis[i]) {
pri[cnt++] = i;
}
for (int j = 0; j < cnt; ++j) {
if (1ll * i * pri[j] >= MAXN) break;
vis[i * pri[j]] = 1;
if (i % pri[j] == 0) {
// i % pri[j] == 0
// 换言之,i 之前被 pri[j] 筛过了
// 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定也是
// pri[j] 的倍数 它们都被筛过了,就不需要再筛了,所以这里直接 break
// 掉就好了
break;
}
}
}
}
筛选欧拉函数
// C++ Version
void pre() {
memset(is_prime, 1, sizeof(is_prime));
int cnt = 0;
is_prime[1] = 0;
phi[1] = 1;
for (int i = 2; i <= 5000000; i++) {
if (is_prime[i]) {
prime[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= cnt && i * prime[j] <= 5000000; j++) {
is_prime[i * prime[j]] = 0;
if (i % prime[j])
phi[i * prime[j]] = phi[i] * phi[prime[j]];
else {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
}
}
}
筛莫比乌斯函数
// C++ Version
void pre() {
mu[1] = 1;
for (int i = 2; i <= 1e7; ++i) {
if (!v[i]) mu[i] = -1, p[++tot] = i;
for (int j = 1; j <= tot && i <= 1e7 / p[j]; ++j) {
v[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
Pollard Rho分解质因数
// C++ Version
list<int> breakdown(int N) {
list<int> result;
for (int i = 2; i * i <= N; i++) {
if (N % i == 0) { // 如果 i 能够整除 N,说明 i 为 N 的一个质因子。
while (N % i == 0) N /= i;
result.push_back(i);
}
}
if (N != 1) { // 说明再经过操作之后 N 留下了一个素数
result.push_back(N);
}
return result;
}
裴蜀定理||贝祖定理
若
a
,
b
a,b
a,b为不为0的整数,那么一定存在
x
,
y
x,y
x,y使得
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)
C
=
a
∗
b
−
a
−
b
C=a*b-a-b
C=a∗b−a−b
为
x
,
y
x,y
x,y为大于等于0的整数时,不能用
a
,
b
a,b
a,b组合而成的最大整数.
类欧几里德算法
属是有点没太看懂 , 太长了懒得看, 赌一手
欧拉定理 & 费马小定理
欧拉定理
与欧拉函数紧密相关的一个定理就是欧拉定理。其描述如下:
若
gcd
(
a
,
m
)
=
1
,
则
a
φ
(
m
)
≡
1
(
m
o
d
m
)
\gcd (a,m) = 1,则{a^{\varphi (m)}} \equiv 1 (\bmod m)
gcd(a,m)=1,则aφ(m)≡1(modm)
费马小定理
若
p
为
素
数
,
g
c
d
(
a
,
p
)
=
1
,
a
p
−
1
≡
1
(
m
o
d
p
)
p为素数,gcd(a,p)=1, a^{p-1}\equiv 1 (\bmod p)
p为素数,gcd(a,p)=1,ap−1≡1(modp)
乘法逆元
扩展欧几里得算法
需要b是素数
// C++ Version
void exgcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
快速幂法
a
x
≡
1
(
m
o
d
b
)
ax\equiv 1 (\bmod b)
ax≡1(modb)
a
x
≡
a
b
−
1
(
m
o
d
b
)
ax\equiv a^{b-1} (\bmod b)
ax≡ab−1(modb)
x
≡
a
b
−
2
(
m
o
d
b
)
x\equiv a^{b-2} (\bmod b)
x≡ab−2(modb)
// C++ Version
inline int qpow(long long a, int b) {
int ans = 1;
a = (a % p + p) % p;
for (; b; b >>= 1) {
if (b & 1) ans = (a * ans) % p;
a = (a * a) % p;
}
return ans;
}
线性求逆元
// C++ Version
inv[1] = 1;
for (int i = 2; i <= n; ++i) {
inv[i] = (long long)(p - p / i) * inv[p % i] % p;
}
线性求解任意n个逆元
// C++ Version
s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2);
// 当然这里也可以用 exgcd 来求逆元,视个人喜好而定.
for (int i = n; i >= 1; --i) sv[i - 1] = sv[i] * a[i] % p;
for (int i = 1; i <= n; ++i) inv[i] = sv[i] * s[i - 1] % p;
线性同余方程
a
x
+
b
y
=
c
ax+by=c
ax+by=c
扩展欧几里得求解
// C++ Version
int ex_gcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int d = ex_gcd(b, a % b, x, y);
int temp = x;
x = y;
y = temp - a / b * y;
return d;
}
中国剩余定理(CRT)
// C++ Version
LL CRT(int k, LL* a, LL* r) {
LL n = 1, ans = 0;
for (int i = 1; i <= k; i++) n = n * r[i];
for (int i = 1; i <= k; i++) {
LL m = n / r[i], b, y;
exgcd(m, r[i], b, y); // b * m mod r[i] = 1
ans = (ans + a[i] * m * b % n) % n;
}
return (ans % n + n) % n;
}
扩展:Garner算法 多个质数表示大数
数论综合应用
取模不为质数的时候,可以将模数分解为不同的质数,最后用中国剩余定理合并答案
G
∑
k
∣
n
(
n
k
)
m
o
d
999991659
{G^{\sum\nolimits_{k|n}^ {} \binom{n}{k} }}\bmod 999 991 659
G∑k∣n(kn)mod999991659
欧拉定理得
G
∑
k
∣
n
(
n
k
)
m
o
d
999991658
m
o
d
999991659
{G^{\sum\nolimits_{k|n}^ {} \binom{n}{k}mod999 991 658 }}\bmod 999 991 659
G∑k∣n(kn)mod999991658mod999991659
999
991
658
999\ 991\ 658
999 991 658不是质数 可以进行分解,然后卢卡斯定理分别求对不同质因数,最后用CRT进行合并就可以得到答案
代码来自链接
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int mod=999911658;
LL n,G,farc[50010],a[5],b[5]={0,2,3,4679,35617},val;
LL fast_pow(LL a,LL b,LL p)//快速幂
{
LL ret=1;
for(;b;b>>=1,a=a*a%p)
ret=ret*(b&1?a:1)%p;
return ret;
}
void init(LL p)//预处理
{
farc[0]=1;
for(LL i=1;i<=p;i++)
farc[i]=farc[i-1]*i%p;
}
LL C(LL n,LL m,LL p)//组合数
{
if(n<m) return 0;
return farc[n]*fast_pow(farc[m],p-2,p)%p*fast_pow(farc[n-m],p-2,p)%p;
}
LL Lucas(LL n,LL m,LL p)//Lucas定理
{
if(n<m) return 0;if(!n) return 1;
return Lucas(n/p,m/p,p)*C(n%p,m%p,p)%p;
}
void CRT()//中国剩余定理
{
for(LL i=1;i<=4;i++)
val=(val+a[i]*(mod/b[i])%mod*fast_pow(mod/b[i],b[i]-2,b[i]))%mod;
}
int main()
{
scanf("%lld%lld",&n,&G);
if(G%(mod+1)==0){
printf("0\n");
return 0;
}//特判
for(LL k=1;k<=4;k++){
init(b[k]);
for(LL i=1;i*i<=n;i++){
if(n%i==0){
a[k]=(a[k]+Lucas(n,i,b[k]))%b[k];
if(i*i!=n){
a[k]=(a[k]+Lucas(n,n/i,b[k]))%b[k];
}
}
}
}//逐一枚举n的约数
CRT();
printf("%lld\n",fast_pow(G,val,mod+1));//注意mod要+1
return 0;
}
威尔逊定理
( p − 1 ) ! ≡ − 1 ( m o d p ) (p-1)! \equiv -1 \ (\bmod \ p) (p−1)!≡−1 (mod p)
升幂原理
卢卡斯定理
( n m ) m o d p = ( ⌊ n / p ⌋ ⌊ m / p ⌋ ) ⋅ ( n m o d p m m o d p ) m o d p \binom{n}{m}\bmod p=\binom{\lfloor{n/p}\rfloor}{\lfloor{m/p}\rfloor}\cdot\binom{n\bmod p}{m\bmod p}\bmod p (mn)modp=(⌊m/p⌋⌊n/p⌋)⋅(mmodpnmodp)modp
// C++ Version
long long Lucas(long long n, long long m, long long p) {
if (m == 0) return 1;
return (C(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}
扩展卢卡斯定理,也就是当 p p p不为质数的时候
#include<algorithm>
#include<iostream>
using namespace std;
long long n,m,p,cnt/*个数*/,pr[1010]/*质数*/,al[1010]/*指数*/;
void exgcd(long long a,long long b,long long &x,long long &y)//扩展欧几里得算法
{
if (!b) return (void)(x=1,y=0);
exgcd(b,a%b,x,y);
long long tmp=x;x=y;y=tmp-a/b*y;
}
long long ny(long long a,long long p)//逆元
{
long long x,y;
exgcd(a,p,x,y);
return (x+p)%p;
}
long long POW(long long a,long long b,long long p)//快速幂
{
long long t=1;
a%=p;
for(;b;b>>=1){
if(b&1) t=t*a%p;
a=a*a%p;
}
return t;
}
long long fac(long long n,long long p,long long ppa)//计算n!
{
if (n==0) return 1;
long long cir=1/*循环节*/,rem=1/*余数*/;
for (long long i=1;i<=ppa;i++) if(i%p) cir=cir*i%ppa;
cir=POW(cir,n/ppa,ppa);
for(long long i=ppa*(n/ppa);i<=n;i++) if(i%p) rem=rem*(i%ppa)%ppa;
return fac(n/p,p,ppa)*cir%ppa*rem%ppa;
}
long long sum_fac(long long n,long long p)//n!中p的个数
{
return n<p?0:sum_fac(n/p,p)+(n/p);
}
long long C(long long n,long long m,long long p,long long ppa)//计算Cnm%pi^ai
{
long long fz=fac(n,p,ppa),fm1=ny(fac(m,p,ppa),ppa),fm2=ny(fac(n-m,p,ppa),ppa);
long long mi=POW(p,sum_fac(n,p)-sum_fac(m,p)-sum_fac(n-m,p),ppa);
return fz*fm1%ppa*fm2%ppa*mi%ppa;
}
void pfd(long long n,long long m)//分解p
{
long long P=p;
for(long long i=2;i*i<=p;i++){
if(!(P%i)){
long long ppa=1;
while(!(P%i)) ppa*=i,P/=i;
pr[++cnt]=ppa;
al[cnt]=C(n,m,i,ppa);
}
}
if(P!=1) pr[++cnt]=P,al[cnt]=C(n,m,P,P);
}
long long crt()//中国剩余定理
{
long long ans=0;
for(long long i=1;i<=cnt;i++){
long long M=p/pr[i],T=ny(M,pr[i]);
ans=(ans+al[i]*M%p*T%p)%p;
}
return ans;
}
long long exlucas(long long n,long long m)//扩展卢卡斯
{
pfd(n,m);
return crt();
}
int main()
{
cin>>n>>m>>p;
cout<<exlucas(n,m);
}
二次剩余
就是求一个
n
2
≡
a
(
m
o
d
p
)
n^2\equiv a( \bmod p)
n2≡a(modp)
首先使用
E
u
l
a
r
Eular
Eular判别准则
a
(
p
−
1
)
/
2
≡
(
a
p
)
≡
{
1
(
m
o
d
p
)
,
若
x
2
≡
a
(
m
o
d
p
)
有
解
−
1
(
m
o
d
p
)
,
若
x
2
≡
a
(
m
o
d
p
)
无
解
a^{(p-1)/2}\equiv \left(\frac{a}{p}\right)\equiv\begin{cases} 1(\bmod \ p),\ \ \ 若x^2\equiv a(\bmod \ p)有解\\ -1(\bmod \ p),若x^2\equiv a(\bmod \ p)无解 \end{cases}
a(p−1)/2≡(pa)≡{1(mod p), 若x2≡a(mod p)有解−1(mod p),若x2≡a(mod p)无解
然后根据
C
o
p
i
l
l
a
Copilla
Copilla来求解
x
=
(
a
+
(
ω
)
)
p
+
1
2
x=(a+\sqrt(ω))^{\frac{p+1}{2}}
x=(a+(ω))2p+1是其中的一个解
p
−
x
p-x
p−x是另一个二次剩余的解
题面
求解方程:
x
2
≡
N
(
m
o
d
p
)
x^2\equiv N(\bmod p)
x2≡N(modp)
输
入
N
以
及
p
输入N以及p
输入N以及p
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll w;
struct num{
ll x,y;
};
num mul(num a,num b,ll p)
{
num ans={0,0};
ans.x=((a.x*b.x%p+a.y*b.y%p*w%p)%p+p)%p;
ans.y=((a.x*b.y%p+a.y*b.x%p)%p+p)%p;
return ans;
}
ll powwR(ll a,ll b,ll p){
ll ans=1;
while(b){
if(b&1)ans=1ll*ans%p*a%p;
a=a%p*a%p;
b>>=1;
}
return ans%p;
}
ll powwi(num a,ll b,ll p){
num ans={1,0};
while(b){
if(b&1)ans=mul(ans,a,p);
a=mul(a,a,p);
b>>=1;
}
return ans.x%p;
}
ll solve(ll n,ll p)
{
n%=p;
if(p==2)return n;
if(powwR(n,(p-1)/2,p)==p-1)return -1;//不存在
ll a;
while(1)
{
a=rand()%p;
w=((a*a%p-n)%p+p)%p;
if(powwR(w,(p-1)/2,p)==p-1)break;
}
num x={a,1};
return powwi(x,(p+1)/2,p);
}
int main()
{
srand(time(0));
int t;
scanf("%d",&t);
while(t--)
{
ll n,p;
scanf("%lld%lld",&n,&p);
if(!n){
printf("0\n");continue;
}
ll ans1=solve(n,p),ans2;
if(ans1==-1)printf("Hola!\n");
else
{
ans2=p-ans1;
if(ans1>ans2)swap(ans1,ans2);
if(ans1==ans2)printf("%lld\n",ans1);
else printf("%lld %lld\n",ans1,ans2);
}
}
}
拉格朗日定理
巨巨巨巨巨巨冷门,开摆
原根
第一个知识点:阶
满足同余式
a
n
≡
1
(
m
o
d
m
)
的
最
小
正
整
数
n
,
称
为
a
模
m
的
阶
,
记
为
δ
m
(
a
)
a^{n}\equiv 1(\bmod m)的最小正整数n,称为a模m的阶,记为\delta_m(a)
an≡1(modm)的最小正整数n,称为a模m的阶,记为δm(a)
第二个知识点:原根
![](https://i-blog.csdnimg.cn/blog_migrate/2c699be360306b496d409f14c7fb92ba.png)
也就是说正整数 g 是 正 整 数 n 的 原 根 , 当 切 记 当 1 < = g < = n − 1 , 且 g 模 n 的 阶 为 φ ( n ) g是正整数n的原根,当切记当1<=g<=n-1,且g模n的阶为\varphi(n) g是正整数n的原根,当切记当1<=g<=n−1,且g模n的阶为φ(n)
g φ ( n ) ≡ 1 ( m o d n ) g^{\varphi(n)} \equiv 1 ( \bmod n) gφ(n)≡1(modn)
![](https://i-blog.csdnimg.cn/blog_migrate/28d2b14ef973acd632bd9282a7dc0c78.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0afe6932c67e52da9ea8f141c34c7141.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5c0d01d2676de9f37b92c8d89a64638d.png)
给出数字n求他的所有原根 输出c/d每隔
题目链接
#include <bits/stdc++.h>
using namespace std;
#define hh cout<<endl;
const int MAXN=1000010;
//pri 质数 rt 原根是否存在 q是都是质数 phi欧拉函数值
int t,p,cnt,tot,ctans,fc[MAXN],ans[MAXN],pri[MAXN],rt[MAXN],q[MAXN],phi[MAXN];
void init () { //筛质数 和欧拉函数
phi[1]=1;
for (int i=2;i<=MAXN-10;i++) {
if (!q[i]) {pri[++tot]=i,phi[i]=i-1;}
for (int j=1;j<=tot&&pri[j]*i<=MAXN-10;j++) {
q[i*pri[j]]=1;
if (i%pri[j]==0) {
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
rt[2]=rt[4]=1; //原根存在定理 判定n是否存在原根
for (int i=2;i<=tot;i++) {
for (int j=1;(1ll*j*pri[i])<=MAXN-10;j*=pri[i]) {rt[j*pri[i]]=1;}
for (int j=2;(1ll*j*pri[i])<=MAXN-10;j*=pri[i]) {rt[j*pri[i]]=1;}
}
return;
}
int gcd (int a,int b) {return (b==0?a:gcd(b,a%b));}
int qpow (int a,int b,int p) {
int res=1;
while (b) {
if (b&1) {res=(1ll*res*a)%p;}
a=(1ll*a*a)%p;
b>>=1;
}
return res;
}
void proc (int p) { //把phi p分解质因数
for (int i=2;i*i<=p;i++) {
if (p%i==0) {
fc[++cnt]=i;
while (p%i==0) {p/=i;}
}
}
if (p>1) {fc[++cnt]=p;}
return;
}
bool chk (int x,int p) {
if (qpow(x,phi[p],p)!=1) {return 0;} //x^phi(p) mod p !=1
for (int i=1;i<=cnt;i++) {
if (qpow(x,phi[p]/fc[i],p)==1) {return 0;}
}
return 1;
}
int findrt (int p) { //原根判定定理 返回的是最小的原根
for (int i=1;i<p;i++) {
if (chk(i,p)) {return i;}
}
return 0;
}
void getrt (int p,int x) { //最小原根的\phi % p !=0 向上找其他原根,原根判定定理
int prod=1;
for (int i=1;i<=phi[p];i++) {
prod=(1ll*prod*x)%p;
if (gcd(i,phi[p])==1) {
ans[++ctans]=prod;
}
}
return;
}
int main () {
init();
// for(int i=1;i<=10;i++) cout<<pri[i]<<" ";hh
// for(int i=1;i<=10;i++) cout<<rt[i]<<" ";hh
// for(int i=1;i<=10;i++) cout<<phi[i]<<" ";hh
// for(int i=1;i<=10;i++) cout<<q[i]<<" ";hh
scanf("%d",&t);
for (int ii=1;ii<=t;ii++) {
int wtf;
scanf("%d%d",&p,&wtf);
if (rt[p]) {
ctans=cnt=0;
proc(phi[p]);
int mn=findrt(p);
getrt(p,mn);
sort(ans+1,ans+ctans+1);
printf("%d\n",ctans);
for (int i=1;i<=ctans/wtf;i++) {printf("%d ",ans[i*wtf]);}
printf("\n");
} else {
printf("0\n\n");
}
}
return 0;
}
BSGS
求解两个东西
一个是
a
x
≡
b
(
m
o
d
p
)
a^x\equiv b (mod \ p)
ax≡b(mod p)
这里要求
a
,
p
a,p
a,p互质
题目:
题目链接
#include<cstdio>
#include<map>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#include<unordered_map>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
std::unordered_map<int,int>map;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline int BSGS(int a,int n,int p,int ad){
map.clear();
reg int m=std::ceil(std::sqrt(p));
reg int s=1;
for(reg int i=0;i<m;i++,s=1ll*s*a%p) map[1ll*s*n%p]=i;
for(reg int i=0,tmp=s,s=ad;i<=m;i++,s=1ll*s*tmp%p)
if(map.find(s)!=map.end())
if(1ll*i*m-map[s]>=0) return 1ll*i*m-map[s];
return -1;
}
inline int exBSGS(int a,int n,int p){
a%=p;n%=p;
if(n==1||p==1) return 0;
reg int cnt=0;
reg int d,ad=1;
while((d=gcd(a,p))^1){
if(n%d) return -1;
cnt++;n/=d;p/=d;
ad=(1ll*ad*a/d)%p;
if(ad==n) return cnt;
}
// std::printf("a: %d n: %d p: %d ad:%d\n",a,n,p,ad);
LL ans=BSGS(a,n,p,ad);
if(ans==-1) return -1;
return ans+cnt;
}
signed main(){
int a=read(),p=read(),n=read();
while(a||p||n){
int ans=exBSGS(a,n,p);
if(~ans) std::printf("%d\n",ans);
else std::puts("No Solution");
a=read();p=read();n=read();
}
return 0;
}
另一个东西是
x
a
≡
b
(
m
o
d
p
)
x^a\equiv b(\bmod \ p )
xa≡b(mod p)
做法就是找到一个
p
p
p的原根
g
g
g
问题转化为
g
a
c
≡
b
(
m
o
d
p
)
g^{a^c} \equiv b(\bmod \ p)
gac≡b(mod p)
这里
g
,
a
,
p
,
b
g,a,p,b
g,a,p,b都可以搞出来 那么他就转化成了上一种式子了。
莫比乌斯反演
前置知识:数论分块,狄利克雷卷积
数论分块
就是求
P2261
G ( n , k ) = ∑ i = 1 n k m o d i G(n,k)=\sum_{i=1}^{n}k\ mod\ i G(n,k)=∑i=1nk mod i
> > ∑ i = 1 n >>\sum_{i=1}^n >>∑i=1n k − i ∗ ⌊ k i ⌋ k-i*\lfloor \frac{k}{i} \rfloor k−i∗⌊ik⌋
> > n ∗ k − ∑ i = 1 n i ∗ ⌊ k i ⌋ >>n*k-\sum_{i=1}^ni*\lfloor \frac{k}{i} \rfloor >>n∗k−∑i=1ni∗⌊ik⌋
O
(
n
)
O(\sqrt{n})
O(n)
这么一个东西,主要是分块思想吧,以前搞过,懒得再搞了,直接copy
//god with me
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+5;
void solve()
{
int n,k;
cin>>n>>k;
int ans=n*k;
for(int l=1,r;l<=n;l=r+1)
{
r=n;
if(k/l!=0)r=min((k/(k/l)),n);
ans-=(k/l)*(l+r)*(r-l+1)/2;
}
cout<<ans<<endl;
}
signed main()
{
int T=1;
//cin>>T;
for(int index=1;index<=T;index++)
{
solve();
}
return 0;
}
狄利克雷卷积
加深理解
也就是说这样表示的
h
(
n
)
=
(
f
∗
g
)
(
n
)
=
∑
d
∣
n
f
(
d
)
g
(
n
d
)
h(n)=(f\ast g)(n)=\sum_{d|n}f(d)g(\frac{n}{d})
h(n)=(f∗g)(n)=∑d∣nf(d)g(dn)
这个也就称作
h
h
h为
f
f
f和
g
g
g的狄利克雷卷积,这里的
∗
\ast
∗是卷积的意思。
再定义一点奇怪的函数
这里的前两个是完全积性函数也就是说不用满足
a
,
b
a,b
a,b互质就可以符合
f
(
a
b
)
=
f
(
a
)
f
(
b
)
f(ab)=f(a)f(b)
f(ab)=f(a)f(b)
他们全部都是积性函数也就是当满足
a
,
b
a,b
a,b互质的时候,都可以符合
f
(
a
b
)
=
f
(
a
)
f
(
b
)
f(ab)=f(a)f(b)
f(ab)=f(a)f(b)
这里要提的是狄利克雷卷积中若
f
,
g
f,g
f,g是积性函数,那么
f
∗
g
f \ast g
f∗g也是积性函数
懒得markdown被迫贴图,算了
除数函数与幂函数
(
f
∗
1
)
(
n
)
=
∑
d
∣
n
f
(
d
)
1
(
n
d
)
=
∑
d
∣
n
f
(
d
)
(f \ast1)(n)=\sum_{d|n}f({d})1(\frac{n}{d})=\sum_{d|n}f(d)
(f∗1)(n)=∑d∣nf(d)1(dn)=∑d∣nf(d)
(
I
d
k
∗
1
)
(
n
)
=
∑
d
∣
n
I
d
k
(
d
)
=
∑
d
∣
n
d
k
(Id_k \ast1)(n)=\sum_{d|n}Id_k({d})=\sum_{d|n}d^k
(Idk∗1)(n)=∑d∣nIdk(d)=∑d∣ndk
欧拉函数
(
φ
∗
1
)
(
n
)
=
∑
d
∣
n
φ
(
d
)
(\varphi \ast1)(n)=\sum_{d|n}\varphi({d})
(φ∗1)(n)=∑d∣nφ(d)
(
φ
∗
1
)
(
p
m
)
=
p
m
(\varphi \ast1)(p^m)=p^m
(φ∗1)(pm)=pm
(
φ
∗
1
)
(
n
)
=
n
a
n
d
φ
∗
1
=
I
d
(\varphi \ast1)(n)=n \\ and\ \ \ \varphi \ast 1 =Id
(φ∗1)(n)=nand φ∗1=Id
单位元
(
ϵ
∗
f
)
(
n
)
=
∑
d
∣
n
ϵ
(
d
)
f
(
n
d
)
=
f
(
n
)
(\epsilon \ast f)(n)=\sum_{d|n}\epsilon({d})f(\frac{n}{d})=f(n)
(ϵ∗f)(n)=∑d∣nϵ(d)f(dn)=f(n)
狄利克雷卷积满足交换律结合律以及对加法的分配率
假设
f
∗
g
=
ϵ
f\ast g=\epsilon
f∗g=ϵ 则称
g
g
g为
f
f
f的狄利克雷逆元
![](https://i-blog.csdnimg.cn/blog_migrate/5169ca0c3ee93a14f66fc345cd26af18.png)
并且它的狄利克雷逆元也是积性函数
题目:hdu6265
求解
∑
d
∣
n
φ
(
d
)
n
d
\sum_{d|n}\varphi (d)\frac{n}{d}
∑d∣nφ(d)dn
![](https://i-blog.csdnimg.cn/blog_migrate/32f050bfd6d4d7625ce8db1cf29e90e9.png)
到此为止 说完了前置知识下边开始莫比乌斯反演
![](https://i-blog.csdnimg.cn/blog_migrate/c9c72af8472d8b71c86cbec4a7df17d1.png)
回忆一下莫比乌斯函数吧,质因数分解 之后如果质数的指数有>1的那么n的莫比乌斯函数为0
莫比乌斯函数还具有以下性质
![](https://i-blog.csdnimg.cn/blog_migrate/20545dd28af9f64b4277bfef6086d4a4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/68031ce90bc40c0ea8d73dce1ea08ff4.png)
莫比乌斯函数线性筛
// C++ Version
void getMu() {
mu[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!flg[i]) p[++tot] = i, mu[i] = -1;
for (int j = 1; j <= tot && i * p[j] <= n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}
莫比乌斯反演
正:如果存在
f
(
n
)
=
∑
d
∣
n
g
(
d
)
f(n)=\sum_{d|n}g({d})
f(n)=∑d∣ng(d),那么有
g
(
n
)
=
∑
d
∣
n
μ
(
d
)
f
(
n
d
)
g(n)=\sum_{d|n}\mu({d})f(\frac{n}{d})
g(n)=∑d∣nμ(d)f(dn)
反:如果存在
f
(
n
)
=
∑
n
∣
d
g
(
d
)
f(n)=\sum_{n|d}g({d})
f(n)=∑n∣dg(d),那么有
g
(
n
)
=
∑
n
∣
d
μ
(
n
d
)
f
(
d
)
g(n)=\sum_{n|d}\mu({\frac{n}{d}})f(d)
g(n)=∑n∣dμ(dn)f(d)
终于是看完看懂了,只看了两道题等我整理一下思路熟悉了公式再补完这部分
杜教筛
详文
从狄利克雷卷积到杜教筛
讲得很好了 没有什么要补充的
map<int,int > Mu;
int sum_mu(int n) {
if(n<maxn) return mu[n];
if(Mu[n]) return Mu[n];
int T=2,res=1;
while(T<=n) {
int pre=T;T=n/(n/T);
res=res-(T-pre+1)*sum_mu(n/T);T++;
}
return Mu[n]=res;
}
题目链接
懒得搞了,看完直接贴
第二题还不是很懂,再想想补理解
代码:
问题一:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
const int maxn = 2000010;
long long T, n, pri[maxn], cur, mu[maxn], sum_mu[maxn];
bool vis[maxn];
map<long long, long long> mp_mu;
long long S_mu(long long x) { // 求mu的前缀和
if (x < maxn) return sum_mu[x];
if (mp_mu[x]) return mp_mu[x]; // 如果map中已有该大小的mu值,则可直接返回
long long ret = (long long)1;
for (long long i = 2, j; i <= x; i = j + 1) {
j = x / (x / i);
ret -= S_mu(x / i) * (j - i + 1);
}
return mp_mu[x] = ret; // 路径压缩,方便下次计算
}
long long S_phi(long long x) { // 求phi的前缀和
long long ret = (long long)0;
long long j;
for (long long i = 1; i <= x; i = j + 1) {
j = x / (x / i);
ret += (S_mu(j) - S_mu(i - 1)) * (x / i) * (x / i);
}
return (ret - 1) / 2 + 1;
}
int main() {
scanf("%lld", &T);
mu[1] = 1;
for (int i = 2; i < maxn; i++) { // 线性筛预处理mu数组
if (!vis[i]) {
pri[++cur] = i;
mu[i] = -1;
}
for (int j = 1; j <= cur && i * pri[j] < maxn; j++) {
vis[i * pri[j]] = true;
if (i % pri[j])
mu[i * pri[j]] = -mu[i];
else {
mu[i * pri[j]] = 0;
break;
}
}
}
for (int i = 1; i < maxn; i++)
sum_mu[i] = sum_mu[i - 1] + mu[i]; // 求mu数组前缀和
while (T--) {
scanf("%lld", &n);
printf("%lld %lld\n", S_phi(n), S_mu(n));
}
return 0;
}
问题二:
#include <cmath>
#include <cstdio>
#include <map>
using namespace std;
const int N = 5e6, NP = 5e6, SZ = N;
long long n, P, inv2, inv6, s[N];
int phi[N], p[NP], cnt, pn;
bool bp[N];
map<long long, long long> s_map;
long long ksm(long long a, long long m) { // 求逆元用
long long res = 1;
while (m) {
if (m & 1) res = res * a % P;
a = a * a % P, m >>= 1;
}
return res;
}
void prime_work(int k) { // 线性筛phi,s
bp[0] = bp[1] = 1, phi[1] = 1;
for (int i = 2; i <= k; i++) {
if (!bp[i]) p[++cnt] = i, phi[i] = i - 1;
for (int j = 1; j <= cnt && i * p[j] <= k; j++) {
bp[i * p[j]] = 1;
if (i % p[j] == 0) {
phi[i * p[j]] = phi[i] * p[j];
break;
} else
phi[i * p[j]] = phi[i] * phi[p[j]];
}
}
for (int i = 1; i <= k; i++)
s[i] = (1ll * i * i % P * phi[i] % P + s[i - 1]) % P;
}
long long s3(long long k) { // 立方和
return k %= P, (k * (k + 1) / 2) % P * ((k * (k + 1) / 2) % P) % P;
}
long long s2(long long k) { // 平方和
return k %= P, k * (k + 1) % P * (k * 2 + 1) % P * inv6 % P;
}
long long calc(long long k) { // 计算S(k)
if (k <= pn) return s[k];
if (s_map[k]) return s_map[k]; // 对于超过pn的用map离散存储
long long res = s3(k), pre = 1, cur;
for (long long i = 2, j; i <= k; i = j + 1)
j = k / (k / i), cur = s2(j),
res = (res - calc(k / i) * (cur - pre) % P) % P, pre = cur;
return s_map[k] = (res + P) % P;
}
long long solve() {
long long res = 0, pre = 0, cur;
for (long long i = 1, j; i <= n; i = j + 1) {
j = n / (n / i);
cur = calc(j);
res = (res + (s3(n / i) * (cur - pre)) % P) % P;
pre = cur;
}
return (res + P) % P;
}
int main() {
scanf("%lld%lld", &P, &n);
inv2 = ksm(2, P - 2), inv6 = ksm(6, P - 2);
pn = (long long)pow(n, 0.666667); // n^(2/3)
prime_work(pn);
printf("%lld", solve());
return 0;
} // 不要为了省什么内存把数组开小,会卡80
Powerful Number 筛
Min_25 筛
洲阁筛
连分数
Stern-Brocot 树与 Farey 序列
Pell 方程
多项式
多项式简介
多项式的度
对于一个多项式 f ( x ) f(x) f(x)他的度(degree)为他的最高项的次数
多项式乘法
f
(
x
)
=
∑
i
=
0
n
a
i
x
i
,
g
(
x
)
=
∑
i
=
0
n
b
i
x
i
f(x)=\sum_{i=0}^na_ix^i , g(x)=\sum_{i=0}^nb_ix^i
f(x)=∑i=0naixi,g(x)=∑i=0nbixi
Q
(
x
)
=
f
(
x
)
⋅
g
(
x
)
=
∑
i
=
1
n
∑
j
=
1
m
a
i
b
j
x
i
+
j
=
c
0
+
c
1
x
+
.
.
.
+
c
n
+
m
x
n
+
m
Q(x)=f(x)\cdot g(x)=\sum_{i=1}^{n}\sum_{j=1}^{m}a_ib_jx^{i+j}=c_0+c_1x+...+c_{n+m}x^{n+m}
Q(x)=f(x)⋅g(x)=∑i=1n∑j=1maibjxi+j=c0+c1x+...+cn+mxn+m
多项式逆元
f
(
x
)
g
(
x
)
≡
1
(
m
o
d
x
n
)
f(x)g(x)\equiv 1(\bmod \ x^n)
f(x)g(x)≡1(mod xn)
则称
g
(
x
)
g(x)
g(x)为
f
(
x
)
f(x)
f(x)在模
x
n
x^n
xn意义下的逆元,称作
f
−
1
(
x
)
f^{-1}(x)
f−1(x)
若满足
d
e
g
g
<
n
deg \ g<n
deg g<n此时
g
g
g唯一
多项式的余数和商
![](https://i-blog.csdnimg.cn/blog_migrate/328944631c49903d8585bb6761695c31.png)
快速傅里叶变换 FFT
就是通过DFT将系数化成点,在通过点运算之后再通过IDFT转回来,中间过程可以蝶形转换分治
复杂度O(nlogn)
学过好多好多遍了 随便搞两个板子好了
推荐一篇写的很好的博客
链接
题目求解a*b
#include <cmath>
#include <cstdio>
#include <iostream>
#define il inline
#define MAXN 3000100
using namespace std ;
// char s1[MAXN], s2[MAXN] ;
int N, M, K, res = 0, ans[MAXN], AA, BB ;
int i, j, k, l, Lim = 1, L, R[MAXN] ;
const double Pi = acos(-1.0) ;
struct node{
double x, y ;
node (double xx = 0, double yy = 0){
x = xx, y = yy ;
}
}A[MAXN], B[MAXN] ;
node operator * (node J, node Q){
return node(J.x * Q.x - J.y * Q.y , J.x * Q.y + J.y * Q.x);
}
node operator + (node J, node Q){
return node(J.x + Q.x , J.y + Q.y);
}
node operator - (node J, node Q){
return node(J.x - Q.x , J.y - Q.y );
}
il int qr(){
int k = 0, f = 1 ;
char c = getchar() ;
while(!isdigit(c)){if(c == '-') f = -1 ;c = getchar() ;}
while(isdigit(c)) k = (k << 1) + (k << 3) + c - 48 ,c = getchar() ;
return k * f ;
}
il void FFT(node *J, double flag){
for(i = 0; i < Lim; i ++)
if(i < R[i]) swap(J[i], J[R[i]]) ;
for(j = 1; j < Lim; j <<= 1){
node T(cos(Pi / j), flag * sin(Pi / j)) ;
for(k = 0; k < Lim; k += (j << 1) ){
node t(1, 0) ;
for(l = 0 ; l < j; l ++, t = t * T){
node Nx = J[k + l], Ny = t * J[k + j + l] ;
J[k + l] = Nx + Ny ;
J[k + j + l] = Nx - Ny ;
}
}
}
}
int main(){
// N = qr() ;
string s1,s2;
cin>>s1>>s2;
N=s1.size();
M=s2.size();
for(i = N - 1; i >= 0; i --) A[AA ++].x = s1[i] - 48;
for(i = M - 1; i >= 0; i --) B[BB ++].x = s2[i] - 48;
// for(int i=0;i<AA;i++) cout<<A[i].x<<" ";cout<<endl;
// for(int i=0;i<BB;i++) cout<<B[i].x<<" ";cout<<endl;
while(Lim < N + M ) Lim <<= 1, L ++ ;
for(i = 0; i <= Lim; i ++ ) R[i] = (R[i >> 1] >> 1) | ((i & 1) << (L - 1)) ;
FFT(A, 1), FFT(B, 1) ;
for(i = 0; i <= Lim; i ++) A[i] = A[i] * B[i] ;
FFT(A, -1) ;
int tot = 0 ;
for(i = 0; i <= Lim; i++) {
ans[i] += (int) (A[i].x / Lim + 0.5) ;
if(ans[i] >= 10)
ans[i + 1] += ans[i] / 10, ans[i] %= 10, Lim += (i == Lim);
}
while(!ans[Lim] && Lim >= 1) Lim -- ;
Lim ++ ;
while( -- Lim >= 0) cout << ans[Lim];
cout<<endl;
return 0 ;
}
快速数论变换NTT
发现原根的性质根单位根的性质一样,直接代进FFT就得到了NTT
博客链接
注意一下这个代码的NR可以开大点
//LuoguP3803
#include <bits/stdc++.h>
typedef long long ll;
const int NR = 1 << 22, g = 3, gi = 332748118, mod = 998244353;
//998244353的一个原根为3且998244353-1=2^23*119,3在模998244353意义下的逆元为332748118
//1e9+7的一个原根为5 且5在1e9+7意义下的逆元为400000003
using namespace std;
int n, m, rev[NR]; //rev[i]为i的二进制翻转
long long a[NR], b[NR];
ll fast_power(ll a, ll k) //快速幂,a为底数,k为指数
{
ll res = 1;
while (k)
{
if (k & 1)
res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
void NTT(ll *a, int n, int type) //NTT,type=1时系数表示法转点值表示法,否则点值表示法转系数表示法
{
for (int i = 0; i < n; ++i) //先将a变成最后的样子
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int i = 1; i < n; i <<= 1)
{ //在这之前NTT与FFT无异
ll gn = fast_power(type ? g : gi, (mod - 1) / (i << 1)); //单位原根g_n
for (int j = 0; j < n; j += (i << 1)) //枚举具体区间,j也就是区间右端点
{
ll g0 = 1;
for (int k = 0; k < i; ++k, g0 = g0 * gn % mod) //合并,记得要取模
{
ll x = a[j + k], y = g0 * a[i + j + k] % mod;
a[j + k] = (x + y) % mod;
a[i + j + k] = (x - y + mod) % mod;
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; ++i) //输入第一个多项式
scanf("%lld", a + i);
for (int i = 0; i <= m; ++i) //输入第二个多项式
scanf("%lld", b + i);
int len = 1 << max((int)ceil(log2(n + m)), 1); //由于NTT需要项数为2的整数次方倍,所以多项式次数len为第一个大于n+m的二的正整数次方
for (int i = 0; i < len; ++i) //求rev
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (max((int)ceil(log2(n + m)), 1) - 1));
NTT(a, len, 1); //系数表示法转点值表示法
NTT(b, len, 1);
for (int i = 0; i <= len; ++i)
a[i] = a[i] * b[i] % mod; //O(n)乘法
NTT(a, len, 0); //点值表示法转系数表示法
ll inv = fast_power(len, mod - 2); //inv为len的逆元(费马小定理求逆元)
for (int i = 0; i <= n + m; ++i) //输出
printf("%lld ", a[i] * inv % mod); //除以len在模mod意义下即为乘以inv
return 0;
}
快速沃尔什变换FWT
生成函数
生成函数简介
生成函数的主要形式:
F
(
x
)
=
∑
n
a
n
k
n
(
x
)
F(x)=\sum_na_nk_n(x)
F(x)=∑nankn(x)
前面是系数 后面是核函数
常见的核函数
1.普通生成函数:
k
n
(
x
)
=
x
n
k_n(x)=x^n
kn(x)=xn
2.指数生成函数:
k
n
(
x
)
=
x
n
n
!
k_n(x)=\frac{x^n}{n!}
kn(x)=n!xn
3.狄利克雷生成函数:
k
n
(
x
)
=
1
n
x
k_n(x)=\frac{1}{n^x}
kn(x)=nx1
忽然发现生成函数需要先搞定多项式,不太能跳啊