ACM知识点-(梅开二度重新出发


主要都根据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=abab
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,ap11(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) ax1(modb)
a x ≡ a b − 1 (   m o d   b ) ax\equiv a^{b-1} (\bmod b) axab1(modb)
x ≡ a b − 2 (   m o d   b ) x\equiv a^{b-2} (\bmod b) xab2(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 Gkn(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 Gkn(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) (p1)!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/pn/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) n2a(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(p1)/2(pa){1(mod p),   x2a(mod p)1(mod p),x2a(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 px是另一个二次剩余的解
题面
求解方程:
x 2 ≡ N (   m o d   p ) x^2\equiv N(\bmod p) x2N(modp)
输 入 N 以 及 p 输入N以及p Np

#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) an1(modm)n,amδm(a)
第二个知识点:原根

也就是说正整数 g 是 正 整 数 n 的 原 根 , 当 切 记 当 1 < = g < = n − 1 , 且 g 模 n 的 阶 为 φ ( n ) g是正整数n的原根,当切记当1<=g<=n-1,且g模n的阶为\varphi(n) gn1<=g<=n1,gnφ(n)

g φ ( n ) ≡ 1 (   m o d   n ) g^{\varphi(n)} \equiv 1 ( \bmod n) gφ(n)1(modn)

给出数字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) axb(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 ) xab(mod p)
做法就是找到一个 p p p的原根 g g g
问题转化为
g a c ≡ b (   m o d     p ) g^{a^c} \equiv b(\bmod \ p) gacb(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 kiik

> > n ∗ k − ∑ i = 1 n i ∗ ⌊ k i ⌋ >>n*k-\sum_{i=1}^ni*\lfloor \frac{k}{i} \rfloor >>nki=1niik

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)=(fg)(n)=dnf(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 fg也是积性函数

懒得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) (f1)(n)=dnf(d)1(dn)=dnf(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 (Idk1)(n)=dnIdk(d)=dndk
欧拉函数
( φ ∗ 1 ) ( n ) = ∑ d ∣ n φ ( d ) (\varphi \ast1)(n)=\sum_{d|n}\varphi({d}) (φ1)(n)=dnφ(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)=dnϵ(d)f(dn)=f(n)
狄利克雷卷积满足交换律结合律以及对加法的分配率

假设 f ∗ g = ϵ f\ast g=\epsilon fg=ϵ 则称 g g g f f f的狄利克雷逆元

并且它的狄利克雷逆元也是积性函数

题目:hdu6265
求解
∑ d ∣ n φ ( d ) n d \sum_{d|n}\varphi (d)\frac{n}{d} dnφ(d)dn

到此为止 说完了前置知识下边开始莫比乌斯反演

回忆一下莫比乌斯函数吧,质因数分解 之后如果质数的指数有>1的那么n的莫比乌斯函数为0
莫比乌斯函数还具有以下性质

莫比乌斯函数线性筛

// 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)=dng(d),那么有 g ( n ) = ∑ d ∣ n μ ( d ) f ( n d ) g(n)=\sum_{d|n}\mu({d})f(\frac{n}{d}) g(n)=dnμ(d)f(dn)
反:如果存在 f ( n ) = ∑ n ∣ d g ( d ) f(n)=\sum_{n|d}g({d}) f(n)=ndg(d),那么有 g ( n ) = ∑ n ∣ d μ ( n d ) f ( d ) g(n)=\sum_{n|d}\mu({\frac{n}{d}})f(d) g(n)=ndμ(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=1nj=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) f1(x)
若满足 d e g   g < n deg \ g<n deg g<n此时 g g g唯一

多项式的余数和商

快速傅里叶变换 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

		忽然发现生成函数需要先搞定多项式,不太能跳啊

符号化方法

普通生成函数

指数生成函数

狄利克雷生成函数

数据结构

图论

ACM技能树

请添加图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

while WA er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值