高斯整数学习笔记

发布时间 2023-08-05 00:18:49作者: clapp

高斯整数及其应用

高斯整数

  • 高斯整数定义:形如\(a+b\cdot i\)的复数被称为高斯整数,其中\(a,b \subseteq \mathbb{Z}\),高斯整数的全体记作\(\mathbb{Z}[i]\)
  • 四则运算:高斯整数的四则运算规则同复数的四则运算规则。
  • 封闭性:设\(\alpha=a+b\cdot i,\beta=c+d\cdot i\)是高斯整数,则\(\alpha+\beta,\alpha-\beta,\alpha\cdot \beta\)均是高斯整数。
  • 范数:对于高斯整数\(\alpha=a+b\cdot i\),记\(N(\alpha)=a^{2}+b^{2}\)\(\alpha\)的范数。显然有\(\alpha\cdot \overline{\alpha}=N(\alpha),\quad N(\alpha\cdot \beta)=N(\alpha)\cdot N(\beta)\)
  • 整除性:高斯整数\(\alpha\)整除高斯整数\(\beta\)当且仅当\(\exists \gamma \subseteq \mathbb{Z}[i],\alpha \cdot \gamma=\beta\),记作\(\alpha|\beta\)
  • 单位与相伴:若\(\epsilon|1\),则称高斯整数\(\epsilon\)是单位,若\(\epsilon\)是单位,则称\(\epsilon\cdot \alpha\)\(\alpha\)的一个相伴。高斯整数的单位有且仅有\(1,-1,i,-i\)
  • 高斯素数定义:若非零的高斯整数\(\pi\)不是单位,且只能够被单位和他的相伴整除,则称\(\pi\)为高斯素数。
  • 定理1:若高斯整数\(\pi\),满足\(N(\pi)=p\),其中\(p\)是有理素数,则\(\pi,\overline{\pi}\)是高斯素数。
    证明

\[\begin{aligned} &假设\pi=\alpha\cdot \beta\Rightarrow N(\pi)=N(\alpha)\cdot N(\beta)=p,那么N(\alpha)=1或N(\beta)=1\\ &这说明\alpha和\beta中有一个是单位,\pi不能分解成两个非单位的高斯素数之积,他是高斯素数 \end{aligned} \]

最大公约数和唯一分解

  • 最大公约数:设\(\alpha,\beta \subseteq \mathbb{Z}[i]\)则称\(\alpha,\beta\)的最大公约数\(\gamma\)为满足以下两个条件的高斯整数:\((i)\gamma|\alpha,\gamma|\beta\quad(ii)\delta|\alpha \wedge \delta|\beta\Rightarrow\delta|\gamma\)
  • 带余除法:设\(\alpha,\beta \subseteq \mathbb{Z}[i]\)\(\beta\neq 0\),则存在高斯整数\(\gamma,\rho\)满足\(\alpha=\beta\cdot \gamma+\rho\)\(0\leq N(\rho)<N(\beta)\)我们称\(\gamma\)为商,\(\rho\)为余数。
    证明

\[\begin{aligned} &假设\alpha=a+b\cdot i,\beta=c+d\cdot i,\frac{a+b\cdot i}{c+d\cdot i}=x+y\cdot i则z+y\cdot i为高斯整数当且仅当\beta|\alpha\\ &令s=\left[ x+\frac{1}{2} \right] ,t=\left[ y+\frac{1}{2} \right]\Rightarrow x+y\cdot i=(s+f)+(t+g)\cdot i,其中f,g \subseteq \mathbb{R}且\left\vert f \right\vert \leq \frac{1}{2} \left\vert g \right\vert \leq \frac{1}{2}\\ &现在令\gamma=s+t\cdot i, \rho=\alpha-\beta\cdot \gamma\\ &那么N(\rho)=N(\beta\cdot (x+y\cdot i-\gamma))=N(\beta)\cdot N(f+g\cdot i)\leq \frac{1}{2}\cdot N(\beta) \end{aligned} \]

  • 定理2:设\(\alpha,\beta \subseteq \mathbb{Z}[i]\)\((i)\exists \gamma \subseteq \mathbb{Z}[i],满足\gamma是\alpha,\beta的最大公约数\quad (ii)\exists \nu,\mu \subseteq \mathbb{Z}[i],\mu\cdot \alpha+\nu\cdot \beta=\gamma\)
    证明

\[\begin{aligned} &设集合S=\left\{ N(\mu\cdot \alpha+\nu\cdot \beta)|\mu,\nu \subseteq \mathbb{Z}[i] \right\},那么集合中一定存在最小元\\ &令\gamma=\mu_0\cdot \alpha+\nu_0\cdot \beta,假设N(\gamma)是S中的最小元,我们证明\gamma就是\alpha,\beta的最大公约数\\ &假设\delta|\alpha,\delta|\beta\Rightarrow\alpha=\delta\cdot \omicron,\beta=\delta\cdot \lambda\Rightarrow\gamma=\mu_0\cdot \omicron\cdot \delta+\nu_0\cdot \lambda\cdot \delta\Rightarrow\delta|\gamma\\ &假设\tau=\mu_1\cdot \alpha+\nu_1\cdot \beta,将\tau对\gamma做带余除法,\tau=\omega\cdot \gamma+\xi,N(\xi)\leq N(\gamma)\\ &容易知道\xi也是形如\mu\cdot \alpha+\nu\cdot \beta的高斯整数,因此\xi只能是0,否则将与N(\gamma)是集合S中最小元素矛盾。\\ &因此有\gamma|\tau\Rightarrow \gamma|\alpha,\gamma|\beta \end{aligned} \]

  • 欧几里得算法:令\(\rho_0=\alpha,\rho_1=\beta\)为非零高斯整数,若连续使用高斯整数的带余除法,可以得到\(\rho_j=\rho_{j+1}\cdot \gamma_{j+1}+\rho_{j+2}\)其中\(N(\rho_{j+2})<N(\rho_{j+1}),j=0,1,2, \ldots ,n-2\)并且\(\rho_{n+1}=0\)。则最后一个非零余数\(\rho_n\)就是\(\alpha,\beta\)的最大公约数。

  • 引理1:若\(\pi\)是高斯素数,\(\alpha,\beta\)是高斯整数,且有\(\pi|\alpha\cdot \beta\),那么有\(\pi|\alpha\)或者\(\pi|\beta\)
    证明

\[\begin{aligned} &假设\pi\not|\beta,由于\pi是高斯素数,\pi和\beta的最大公约数只能是单位\\ &所以由定理2存在\mu,\nu满足\mu\cdot \beta+\nu\cdot \pi=1\Rightarrow \mu\cdot \beta\cdot \alpha+\nu\cdot \pi\cdot \alpha=\alpha\Rightarrow \pi|\alpha\\ &同理,\pi\not|\alpha\Rightarrow \pi|\beta,引理得证。\\ &这个引理还可以推广到多个数的情形:高斯素数\pi|\alpha_1\cdot \alpha_2\cdots\alpha_n\Rightarrow \exists j \subseteq [1,n],\pi|\alpha_j \end{aligned} \]

  • 唯一分解定理:设\(\gamma\)是非零非单位的高斯整数,则\((i)\gamma能分解为一些高斯素数的乘积\quad (ii)该分解在某种意义上是唯一的,即若\gamma=\rho_1\cdot \rho_2\cdots\rho_s=\pi_1\cdot \pi_2\cdots\pi_t\Rightarrow s=t,并且对这些项重新排列,可以使得\rho_i与\pi_i相伴,i=1,2, \cdots ,s\)
    证明

\[\begin{aligned} &当N(\gamma)=2时,由定理1可知,\gamma是高斯素数\\ &现在假设N(\gamma)>2,且任意范数小于N(\gamma)的高斯整数都能表示为一些高斯素数的乘积\\ &若\gamma是高斯素数,则他显然可以表示为高斯素数的乘积,若他不是高斯素数\\ &则\gamma=\theta\cdot \mu,1<N(\theta)<N(\gamma),1<N(\mu)<N(\gamma),由归纳假设,\theta,\mu都能分解成一些高斯素数之积,故\gamma也能分解成一些高斯素数之积。\\ &我们同样使用第二数学归纳法证明(ii)\\ &若\gamma是高斯素数,则他的分解显然唯一,若其不是高斯素数\\ &假设\gamma=\rho_1\cdot \rho_2\cdots\rho_s=\pi_1\cdot \pi_2\cdots\pi_t\Rightarrow \rho_1|\pi_1\cdot \pi_2\cdots\pi_t\Rightarrow\exists 1\leq j\leq t,\rho_1|\pi_j\\ &我们对\pi_1\cdots \pi_t重新排列,使得\rho_1|\pi_1\Rightarrow\rho_1和\pi_1相伴。然后两边同时除以\pi_1对剩余部分使用归纳假设。\\ &这里我们可以看出唯一分解成立的关键就是引理1的成立。 \end{aligned} \]

高斯整数与平方和

求出所有的正整数\(a,b\)满足\(a^{2}+b^{2}=n\)

  • 定理3:形如\(4k+1\)的有理质数\(p\)可以分解为两个有理整数的平方和。
    证明

\[\begin{aligned} &-1为4k+1形素数的二次剩余,于是存在有理整数t满足t^2\equiv -1(mod p)\\ &p|(t-i)\cdot (t+i),若p是高斯素数,则由引理1,p|t-i或p|t+i,而能整除p的高斯整数都形如p\cdot (a+b\cdot i),t+i,t-i显然不满足这种形式。\\ &于是p不是高斯素数,那么存在\alpha,\beta \subseteq \mathbb{Z}[i],N(\alpha)>1,N(\beta)>1,\alpha\cdot \beta=p\Rightarrow N(p)=p^2=N(\alpha)\cdot N(\beta)\Rightarrow N(\alpha)=N(\beta)=p\\ &从而p可以写成两个有理整数的平方和。\\ &假设p=a^2+b^2则p=(a+b\cdot i)\cdot (a-b\cdot i),由定理1,a+b\cdot i与a-b\cdot i均是高斯素数。\\ &考虑如何求出a和b,如果能找到t,满足t^2\equiv-1\left( \mod p\right),那么只需要求出\operatorname{gcd}(t+i,p)就能得到a,b\\ &而有一半的数都是p的二次剩余,因此我们可以随机,找到p的一个二次非剩余k,则k^{\frac{p-1}{2}}\equiv -1(\mod p)\\ &取t=k^{\frac{p-1}{4}}即可。 \end{aligned} \]

  • 定理4:形如\(4k+3\)的有理素数\(p\)是高斯素数。
    证明

\[\begin{aligned} &首先对于任意正整数a,a^2\equiv 0或1(\mod 4)\Rightarrow p不能表示为两个有理整数的平方和。\\ &假设p不是高斯素数,则存在\alpha,\beta \subseteq \mathbb{Z}[i],N(\alpha)>1,N(\beta)>1,\alpha\cdot \beta=p\Rightarrow N(p)=p^2=N(\alpha)\cdot N(\beta)\Rightarrow N(\alpha)=N(\beta)=p\\ &从而p可以写成两个有理整数的平方和,这产生了矛盾\\ &于是p是高斯素数。 \end{aligned} \]

  • 平方和

\[\begin{aligned} &假设n是正整数,且有如下分解形式n=2^w p_1^{a_1}p_2^{a_2}\cdots p_s^{a_s}q_1^{b_1}q_2^{b_2}\cdots q_t^{b_t},其中p_i是形如4k+1的有理素数,q_i是形如4k+3的有理素数。\\ &接下来将n分解为高斯素数之积n=i^w(1-i)^{2w}(c_1+d_1i)^{a_1}(c_1-d_1i)^{a_1}\cdots ( c_s+d_si)^{a_s}(c_s-d_si)^{a_s}q_1^{b_1}q_2^{b_2}\cdots q_t^{b_t}\\ &将n分解为a^2+b^2即将n分解为(a+bi)\cdot (a-bi)\\ &设a-bi=\epsilon\cdot (1-i)^e(c_1+d_1i)^{f_1}(c_1-d_1i)^{g_1}\cdots (c_s+d_si)^{f_s}(c_s-d_si)^{g_s}q_1^{h_1}q_2^{h_2}\cdots q_t^{h_t}\\ &则a+bi=\overline{\epsilon}\cdot (1+i)^e(c_1-d_1i)^{f_1}(c_1+d_1i)^{g_1}\cdots (c_s-d_si)^{f_s}(c_s+d_si)^{g_s}q_1^{h_1}q_2^{h_2}\cdots q_t^{h_t}\\ &(a-bi)\cdot (a+bi)=2^e(c_1+d_1i)^{f_1+g_1}(c_1-d_1i)^{f_1+g_1}\cdots (c_s+d_si)^{f_s+g_s}(c_s-d_si)^{f_s+g_s}q_1^{2h_1}q_2^{2h_2}\cdots q_t^{2h_t}\\ &因此需要满足e=w,f_i+g_i=a_i(i=1,2, \cdots ,s),2h_i=b_i(i=1,2, \cdots,t),这样我们就得到了要求的a,b\\ &我们可以看到,正整数n要想分解成两个正整数的平方和,充要条件是n的形如4k+3的质因子的次数均为偶数。 \end{aligned} \]

一些例子

  • \(n\times m\)的矩形放入坐标系中,使得矩形的四个角坐标均为整点,问所有不同的放置方式下矩形包含的完整\(1\times 1\)正方形数量之和。两个放置方式相同当且仅当他们之间可以仅通过平移重合。\(n,m\leq 10^{18}\)2022牛客暑期多校训练营7 D.The Pool

\[\begin{aligned} &将矩形ABCD中顶点A放置到原点,假设AB=n,AD=m\\ &考虑B的可行位置,即找到整数a,b满足a^2+b^2=n^2,同时需要判断一下对应的D点是否在整点上。\\ &假设B坐标(a,b),D坐标(c,d)\\ &s_1=以a,b为边长的直角三角形中1\times 1方格的数量\\ &s_2=以c,d为边长的直角三角形中1\times 1方格的数量\\ &则ans=(s_1+s_2)\times 2+(a-c)\times (d-b)\\ &s_1=\sum\limits_{i=0}^{a-1}\left\lfloor \frac{b\cdot x}{a} \right\rfloor可以方便的用类欧几里得算法求解。 \end{aligned} \]

code:

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
#define int long long

using namespace std;

const int mo = 998244353;
const int d[12] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

struct gauss {
    __int128 a, b;
    gauss() {}
    gauss(int o, int p) {
        a = o;
        b = p;
    }
    bool operator==(const gauss& o) const { return (a == o.a) && (b == o.b); }
    bool operator<(const gauss& o) const {
        return (a < o.a) || ((a == o.a) && (b < o.b));
    }
    gauss operator-(const gauss& o) const { return gauss(a - o.a, b - o.b); }
    gauss operator*(const gauss& o) const {
        return gauss(a * o.a - b * o.b, a * o.b + b * o.a);
    }
    gauss operator%(const gauss& o) const {
        int t1 = floor(((long double)a / o.b + (long double)b / o.a) /
                           ((long double)o.a / o.b + (long double)o.b / o.a) +
                       0.5);
        int t2 = floor(((long double)b / o.b - (long double)a / o.a) /
                           ((long double)o.a / o.b + (long double)o.b / o.a) +
                       0.5);
        return gauss(a, b) - o * gauss(t1, t2);
    }
};

int n, m, bo[1000005], prime[200005], s0 = 0, cnt = 0, div1[1005][2],
                                      div3[1005][3], s1 = 0, s3 = 0, s2 = 0;
gauss divid[1005], possible[2000005], st[1005][1005];

__int128 abss(__int128 o) { return (o > 0) ? o : (-o); }

int gcd(int o, int p) { return (!p) ? o : gcd(p, o % p); }

int mi(int o, int p, int mo) {
    int yu = 1;
    while (p) {
        if (p & 1) yu = (__int128)yu * o % mo;
        o = (__int128)o * o % mo;
        p >>= 1;
    }
    return yu;
}

bool test(int o, int p) {
    if (mi(o, p - 1, p) != 1) return 0;
    int yu = p - 1;
    while (!(yu & 1)) {
        yu >>= 1;
        if (mi(o, yu, p) == (p - 1)) return 1;
    }
    return mi(o, yu, p) == 1;
}

bool Miller_Rabin(int o) {
    if (o <= 1) return 1;
    for (int i = 0; i < 12; i++)
        if (o == d[i]) return 1;
    for (int i = 0; i < 12; i++)
        if (!test(d[i], o)) return 0;
    return 1;
}

int Pollard_Rho(int o) {
    int x1 = 1ll * rand() * rand() * rand() * rand() % o + 1, c = rand() + 1,
        y = x1;
    for (int lim = 1;; lim = min(lim, lim << 1)) {
        int cnt = 1;
        for (int i = 1; i <= lim; i++) {
            x1 = ((__int128)x1 * x1 + c) % o;
            y = ((__int128)y * y + c) % o;
            y = ((__int128)y * y + c) % o;
            cnt = (__int128)cnt * abs(x1 - y) % o;
        }
        int yu = gcd(cnt, o);
        if (yu > 1) return yu;
    }
    return o;
}

gauss gauss_mi(gauss o, int p) {
    gauss yu = gauss(1, 0);
    while (p) {
        if (p & 1) yu = yu * o;
        o = o * o;
        p >>= 1;
    }
    return yu;
}

gauss gauss_gcd(gauss o, gauss p) {
    if (p == gauss(0, 0)) return o;
    return gauss_gcd(p, o % p);
}

gauss split(int o) {
    int yu = 1ll * rand() * rand() * rand() * rand() % o + 1;
    while (mi(yu, (o - 1) >> 1, o) != (o - 1)) {
        yu = 1ll * rand() * rand() * rand() * rand() % o + 1;
    }
    yu = mi(yu, (o - 1) >> 2, o);
    gauss t = gauss_gcd(gauss(yu, -1), gauss(o, 0));
    int a1 = abss(t.a), a2 = abss(t.b);
    if (a1 > a2) swap(a1, a2);
    return gauss(a1, a2);
}

void insert(gauss o) { possible[++s0] = gauss(abss(o.a), abss(o.b)); }

void dfs(int o, gauss p) {
    if (o > s1) {
        gauss yu = p;
        insert(yu);
        insert(yu * gauss(0, 1));
        return;
    }
    gauss yu = gauss(1, 0);
    st[o][0] = gauss(1, 0);
    for (int i = 1; i <= div1[o][1]; i++)
        st[o][i] = st[o][i - 1] * gauss(divid[o].a, -divid[o].b);
    for (int i = 0; i <= div1[o][1]; i++) {
        dfs(o + 1, p * st[o][div1[o][1] - i] * yu);
        yu = yu * divid[o];
    }
}

int Mo(int o) { return (o >= mo) ? (o - mo) : o; }

int calc(__int128 a, __int128 b, __int128 c, __int128 n) {
    if (a == 0) return ((b / c) % mo) * ((n + 1) % mo) % mo;
    if ((a >= c) || (b >= c)) {
        int s1 = ((n * (n + 1) / 2) % mo * ((a / c) % mo)) % mo;
        int s2 = ((n + 1) % mo) * ((b / c) % mo) % mo;
        return Mo(Mo(s1 + s2) + calc(a % c, b % c, c, n));
    }
    int s1 = ((n % mo) * ((a * n + b) / c) % mo -
              calc(c, c - b - 1, a, (a * n + b) / c - 1)) %
             mo;
    return Mo(s1 + mo);
}

signed main() {
    srand((unsigned)time(NULL));
    for (int i = 2; i <= 100000; i++) {
        if (!bo[i]) prime[++cnt] = i;
        for (int j = 1; (j <= cnt) && (i * prime[j] <= 100000); j++) {
            bo[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
        }
    }
    int T;
    cin >> T;
    while (T--) {
        scanf("%lld%lld", &n, &m);
        int gg = gcd(n, m);
        s1 = s2 = s3 = s0 = 0;
        for (int i = 1; i <= cnt; i++)
            if (gg % prime[i] == 0) {
                if (prime[i] % 4 == 1) {
                    s1++;
                    div1[s1][0] = prime[i];
                    div1[s1][1] = 0;
                    while (gg % prime[i] == 0) div1[s1][1]++, gg /= prime[i];
                } else if (prime[i] % 4 == 3) {
                    s3++;
                    div3[s3][0] = prime[i];
                    div3[s3][1] = 0;
                    div3[s3][2] = 1;
                    while (gg % prime[i] == 0) {
                        div3[s3][1]++;
                        gg /= prime[i];
                        div3[s3][2] *= prime[i];
                    }
                } else
                    while (gg % 2 == 0) gg >>= 1, s2++;
            }
        while (!Miller_Rabin(gg)) {
            int yu = Pollard_Rho(gg);
            gg /= yu;
            if (yu > gg) swap(yu, gg);
            if (yu > 1) {
                if (yu % 4 == 1) {
                    s1++;
                    div1[s1][0] = yu;
                    div1[s1][1] = 1;
                    while (gg % yu == 0) div1[s1][1]++, gg /= yu;
                } else {
                    s3++;
                    div3[s3][0] = yu;
                    div3[s3][1] = div3[s3][2] = 1;
                    while (gg % yu == 0) {
                        div3[s3][1]++;
                        gg /= yu;
                        div3[s3][2] *= yu;
                    }
                }
            }
        }
        if (gg > 1) {
            if (gg % 4 == 1) {
                s1++;
                div1[s1][0] = gg;
                div1[s1][1] = 1;
            } else {
                s3++;
                div3[s3][0] = gg;
                div3[s3][1] = 1;
                div3[s3][2] = gg;
            }
        }
        s2 <<= 1;
        for (int i = 1; i <= s1; i++) div1[i][1] <<= 1;
        for (int i = 1; i <= s3; i++) div3[i][1] <<= 1;
        for (int i = 1; i <= s1; i++) divid[i] = split(div1[i][0]);
        gauss yu = gauss_mi(gauss(1, -1), s2);
        for (int i = 1; i <= s3; i++) yu = yu * gauss(div3[i][2], 0);
        dfs(1, yu);
        for (int i = 1; i <= s0; i++) {
            possible[i].a = abss(possible[i].a);
            possible[i].b = abss(possible[i].b);
        }
        for (int i = 1; i <= s0; i++)
            if (possible[i].a > possible[i].b)
                swap(possible[i].a, possible[i].b);
        sort(possible + 1, possible + s0 + 1);
        possible[0] = gauss(0, 0);
        int ans = 0;
        gg = gcd(n, m);
        for (int i = 1; i <= s0; i++) {
            if (possible[i] == possible[i - 1]) continue;
            if (!possible[i].a) continue;
            if (!possible[i].b) continue;
            int a = possible[i].a * (n / gg), b = possible[i].b * (n / gg);
            int c = possible[i].b * (m / gg), d = possible[i].a * (m / gg);
            int yu = calc(b, 0, a, a - 1) * 2 % mo;
            yu = (yu + calc(c, 0, d, d - 1) * 2) % mo;
            yu = (yu + (__int128)(c - a) * (b - d) % mo) % mo;
            yu = (yu + mo) % mo;
            if (possible[i].a != possible[i].b) yu = yu * 2 % mo;
            ans = (ans + yu) % mo;
        }
        ans = (ans + (__int128)n * m % mo) % mo;
        if (n != m) ans = ans * 2 % mo;
        printf("%lld\n", ans);
    }
    return 0;
}