这部分主要讲约数和同余
唯一分解定理的推论

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | vector<int> fac;
  void solve(int n) {     for(int i = 1; i*i <= n; i++) {         if(n % i) continue;         fac.push_back(i);         if(i != n/i) fac.push_back(n/i);     } }
  int main() {     const int n = 100;     solve(n);
      for(auto x : fac) printf("%d ", x); }
   | 
 
把一个数分解成最接近的两个数相乘
实际上,是要找出 ⩾n 的
能够被 n 整除的最小的整数 i
(i,n/i) 就是答案
1 2 3 4 5 6 7 8 9 10
   | pair<int, int> crack(int n) {     int i = sqrt(n);     int j = n / i;     while (n % i) {         i++;         j = n / i;     }
      return make_pair(i, j); }
  | 
 
由此可见,一个数约数个数的上界是 2n
求1到N每个数的正约数集合——倍数法
对每一个数 d,[1,N] 中以 d 作为约数的数
{d,2d,3d,⋯,⌊n/d⌋}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | const int maxn = 1e6 + 10; const int N = 1e6; vector<int> fac[maxn];
  void solve(int n) {     for(int i = 1; i <= n; i++) {         for(int j = 1; j <= n/i; j++)             fac[i*j].push_back(i);     }
      for(int i = 1; i <= n; i++) {         for(auto x : fac[i]) printf("%d ", x);         printf("\n");     } }
  int main() {     solve(N); }
   | 
 
算法实践


反素数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
   | const ll inf = 0x3f3f3f3f; const int maxn = 2*1e9 + 5; int n;
  const int p[] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; int c[11]; ll val0 = inf, cnt0 = 1;
  void dfs(ll id, ll val, ll cnt) {     if(id == 11) {         if(cnt > cnt0 || (cnt == cnt0 && val < val0)) {             cnt0 = cnt;             val0 = val;         }         return;     }
      ll val2 = val;     _rep(i, 0, c[id-1]) {         if(val2 > n) return;
          c[id] = i;         dfs(id+1, val2, cnt*(i+1));         val2 *= p[id];     } }
  void init() {     memset(c, 0, sizeof(c));     c[0] = inf; }
 
  int main() {     freopen("input.txt", "r", stdin);     cin >> n;     init();     //debug(val0);
      dfs(1, 1, 1);     printf("%lld\n", val0); }
   | 
 
取整函数的性质
Acwing199


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | ll n, k, ans; ll l, r;
  void init() {     ans = n*k; }
  void solve() {     for(l = 1; l <= n; l = r+1) {         r = k/l == 0 ? n : min(n, k/(k/l));         ans -= (k/l) * (r + l) * (r-l+1) / 2;     }     cout << ans << endl; }
  int main() {     //freopen("input.txt", "r", stdin);     cin >> n >> k;
      init();     solve(); }
   | 
 
最大公约数
∀a,b∈N,gcd(a,b)⋅lcm(a,b)=a⋅b
证明,d=gcd(a,b),a0=a/d,b0=b/d
gcd(a0,b0)=1,lcm(a0,b0)=a0⋅b0
lcm(a,b)=lcm(a0⋅d,b0⋅d)=d⋅lcm(a0,b0)=d⋅a0⋅b0
=a⋅b
公约数算法
对于 a,b 的任意公约数 d
d∣a, d∣b⇒d ∣ (a−b)
d∣[a,a−b],d∣[b,a−b]
∀a,b∈N,a⩾b,gcd(a,b)=gcd(a,a−b) ∀a,b∈N,gcd(2a,2b)=2⋅gcd(a,b)
Eucilid算法
∀a,b∈N,b=0,gcd(a,b)=gcd(b,amodb)
证明其实也比较简单
a=q⋅b+r
对于任意公约数 d, d∣a,d∣(q⋅b)
⇒d∣(a−qb)⇔d∣r
d 也是 b,r 的公约数,二者的公约数集合相同
1 2 3
   | int gcd(int a, int b) {     return b ? gcd(b, a%b) : a; }
  | 
 
算法实践
在 gcd, lcm 问题中,恒等式
lcm=gcda⋅b
是非常重要的一个理论依据
其中有一些隐含条件
a⋅b=G⋅L,G⩽L
G∈[factors of a]
UVA11889
暴力做法
b=ac⋅gcd(a,b)
对 a 的因子从小到大排序
我们可以暴力枚举 a 的因子 x,让 b=c⋅x/a
然后检查 a⋅b/gcd(a,b)=c?
约数中的集合思想
b=gcd(a,b)⋅c/a
如果 gcd(a,b)=1,b=c/a 就是答案
如果不呢?那么令 b0=c/a,b=b0∗(p1c1⋯pkck)
这里有一种 过滤因子 的思想

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
   |  ll A, B, C, ans;
  inline ll gcd(ll a, ll b) {     return b ? gcd(b, a % b) : a; }
  void solve() {     bool fl = true;     if(C < A || C % A) {         fl = false;     }
      if(!fl) {         printf("NO SOLUTION\n");         return;     }
      ll B = C/A, k = 1;     if(gcd(A, B) == 1) {         printf("%lld\n", B);         return;     }
      while (gcd(A, B) > 1) {         int d = gcd(A, B);         k *= d;         A /= d;     }
      printf("%lld\n", B*k); }
  void init() {     // }
  int main() {     freopen("input.txt", "r", stdin);     int kase;     cin >> kase;
      while (kase--) {         scanf("%lld%lld", &A, &C);         init();
          solve();     } }
 
  | 
 
约数问题中的搜索算法
在约数问题中,常见的策略有 dfs
可以用 dfs 枚举可能的所有因子,存在 res[⋯] 中
Acwing200

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
   |  typedef pair<int, int> PII; const int maxn = 50000 + 10;
  inline ll gcd(ll a, ll b) {     return b ? gcd(b, a%b) : a; }
  ll a0, a1, b0, b1;
  bool fl[maxn]; vector<int> primes;
  void getPrimes(int n) {     memset(fl, 0, sizeof(fl));     primes.clear();     for(int i = 2; i <= n; i++) {         if(fl[i]) continue;         primes.push_back(i);         for(int j = i; j <= n/i; j++) fl[i*j] = true;     } }
  vector<PII> fac; void prework(ll d) {     for(const auto& p : primes) {         if(d % p == 0) {             int c = 0;             while (d % p == 0) c++, d /= p;             fac.push_back(make_pair(p, c));         }     }     if(d > 1) fac.push_back(make_pair(d, 1)); }
  void dfs(ll k, ll x, vector<ll>& div) {     if(k == fac.size()) {         div.push_back(x);         return;     }
      for(int i = 0; i <= fac[k].second; i++) {         dfs(k+1, x, div);         x *= fac[k].first;     } }
 
  void init() {     fac.clear(); }
  int main() {     freopen("input.txt", "r", stdin);     getPrimes(maxn);
      int kase;     cin >> kase;
      while (kase--) {         init();         scanf("%lld%lld%lld%lld", &a0, &a1, &b0, &b1);
          prework(b1);         vector<ll> div;         dfs(0, 1, div);
          ll ans = 0;         for(auto x : div) {             if(gcd(x, a0) == a1 && gcd(x, b0) * b1 == x * b0) ans++;         }         printf("%lld\n", ans);     } }
 
  |