ZHX 清北数学 Day 1 笔记

ZHX 清北数学 Day 1 笔记

一、模运算

除法的一些小性质

首先,我们看一下小学的除法:

$ a \div b = c \cdots \cdots d $

这里有一个显然的性质:

$ a = b \times c + d $

大小关系:$ b > d \ge 0 $

模运算的性质

$ (a + b) \bmod c = (a \bmod c + b \bmod c) \bmod c $

证明:

这里我们设:$ a = xc + a', b = yc + b' $

那么,$ (a + b) \bmod c = (xc + a' + yc + b') \bmod c $

我们把这个可以整除的部分提出去,$ (a + b) \bmod c = (a' + b') \bmod c = (a \bmod c + b \bmod c) \bmod c $

因此,得证

同样的,我们可以得出:

$ (a - b) \bmod c = (a \bmod c - b \bmod c) \bmod c $

$ (a \times b) \bmod c = \lbrack (a \bmod c) \times (b \bmod c) \bmod c \rbrack $

除法的话可以代个数进去试试,会出问题。

例题

求 $ (a + b) \bmod c $, $ (a - b) \bmod c $, $ (a \times b) \bmod c $

比较简单,代码如下:

# include <bits/stdc++.h>

using namespace std;

int a, b, p;

signed main () {

    cin >> a >> b >> p;

    cout << (a % p + b) % p << endl;

    cout << (a % p - b) % p << endl;

    cout << (a % p * b) % p << endl;

    return 0;

}

负数取模

这里看一下 C++ 如何处理负数除法:

$ -3 \bmod 2 = -1 $

C++ 是忽略负号,然后计算,最后加上负号,这样的话会出问题。所以我们可以加上 模数,再次取模,即:

$ (-3 \bmod 2 + 2) \bmod 2 $

写出公式:$ (a \bmod b + b) \bmod b $

补充·int 转 long long

这里有两种方式:

  1. (long long) a * b

  2. 1ll * a * b

例题

求 $ n ! \bmod p $。

比较简单,代码如下:

# include <bits/stdc++.h>

using namespace std;

signed main () {

    int n, p, m;

    cin >> n >> p;

    int ans = 1;

    for (register int i = 1; i <= p; ++ i) {

        ans *= n;

        ans %= m;

    }

    cout << ans % m << endl;

    return 0;

}

二、gcd 和 lcm

这个地方也比较简单。主要讲证明。

定义

gcd:最大公因数,即两个数所有因数中,公共的因数里面最大的那一个

lcm:最小公倍数,即两个数所有倍数中,公共的倍数里面最小的那一个

证明辗转相除法

求 $ gcd (a, b) $,设为 $ g $

设 $ a = a'g $, $ b = b'g $

那么很显然,$ gcd (a', b') = 1 $,即 $ a' $ 和 $ b' $ 互质

接下来证明,$ gcd (a, b) = gcd (a - b, b) $

这里我们知道 $ gcd (a, b) = g $,所以我们只需要证明 $ gcd (a - b, b) = g $

然后 $ gcd (a'g - b'g, b'g) = g $

得证

同样的方式,我们可以发现 $ gcd (a - 2b, b) = g $, $ gcd (a - 3b, b) \dots $

所以化简 $ gcd (a, b) = gcd (a \bmod b, b) $

然后辗转相除即可。

代码如下:

inline int gcd (int x, int y) {

    return y ? gcd (y, x % y) : x;

}

gcd 的复杂度

首先 $ gcd (a, b) = gcd (a \bmod b, b) (a \ge b) $

然后推出 $ a \bmod b < a $

如果 $ a \bmod b < \frac{1}{2}a $,那么复杂度显然是 $ log $ 的

考虑怎么证明

分类讨论:

  1. $ b \le \frac{1}{2}a $

推出 $ a \bmod b < b \le \frac{1}{2}a $

  1. $ \frac{1}{2}a < b < a $

推出 $ a \bmod b = a - b \times 1 $ (被除数 - 除数 × 商 = 余数)

所以 $ a \bmod b = a - b < \frac{1}{2}a $

所以得证

最小公倍数

有一个十分有趣的定理,很神奇,大家可以自己证明,比较简单:

$ x \times y = gcd (x, y) \times lcm (x, y) $

处理一下溢出,所以我们可以得到代码:

inline int lcm (int x, int y) {

    return x / gcd (x, y) * y;

}

三、快速幂

这里我们讲一下简单的版本,首先举个例子,我们求 $ x^{37} $

然后我们发现,按照普通的乘法,我们需要做 $ 37 $ 次乘法,但是我们考虑化简

$ x^{37} = x^{18} \times x^{18} \times x $,按照这种做法,我们可以只需要做 $ 7 $ 次乘法

是不是很不错,下面是规则:

\[ x^n = \begin{cases} x^{n / 2} \times x^{n / 2} \times x & x \bmod 2 = 1 \\ x^{n / 2} \times x^{n / 2} & x \bmod 2 = 0 \end{cases} \]

这样的话就快了很多,然后我们看一下代码:(加上取模)

inline int qpow (int x, int y, int z) {

    if (y == 0) return 1;

    if (y == 1) return x;

    int tmp = qpow (x, y / 2, z);

    if (y & 1) return tmp * tmp % z * x % z;

    else return tmp * tmp % z;

}

例题

求 $ x \times y \bmod p $

追求快速的做法,我们考虑使用和 快速幂一样的思路,把幂运算改成乘法运算,然后就是一样的了,代码如下:

inline int qtimes (int x, int y, int z) {

    if (y == 0) return 0;

    if (y == 1) return x;

    int tmp = qtimes (x, y / 2, z);

    if (y & 1) return (tmp + tmp) % z * x % z;

    else return (tmp + tmp) % z;

}

四、矩阵

定义

矩阵的话就是一组 $ n \times m $ 的数组,比较好理解

例如这是一个 $ 2 \times 3 $ 的矩阵:

\[\left[\begin{array}{l} 1 & 1 & 4 \\ 5 & 1 & 4 \end{array}\right] \]

矩阵加减法

矩阵加减法就是对应位置相加减,即:

\[\left[\begin{array}{l} 1 & 1 & 4 \\ 5 & 1 & 4 \end{array}\right] + \left[\begin{array}{l} 1 & 1 & 4 \\ 5 & 1 & 4 \end{array}\right] = \left[\begin{array}{l} 2 & 2 & 8 \\ 10 & 2 & 8 \end{array}\right] \]

矩阵数乘

用一个数去乘这个矩阵里面所有的数,即:

\[\left[\begin{array}{l} 1 & 1 & 4 \\ 5 & 1 & 4 \end{array}\right] \times 2 = \left[\begin{array}{l} 2 & 2 & 8 \\ 10 & 2 & 8 \end{array}\right] \]

矩阵乘法

这个也比较简单,我们用两个矩阵来相乘,只有 $ n \times m $ 的矩阵和 $ m \times k $ 的矩阵才可以相乘。

比如:

\[\left[\begin{array}{l} 1 & 1 & 4 \\ 5 & 1 & 4 \end{array}\right] \times \left[\begin{array}{l} 1 & 1 \\ 0 & 1 \\ 0 & 1 \end{array}\right] = \left[\begin{array}{l} 1 & 6 \\ 5 & 10 \end{array}\right] \]

运算法则是答案矩阵是 $ n \times k $ 的,对应答案矩阵中的第 $ l $ 行第 $ r $ 列的数等于第一个矩阵中的第 $ l $ 行,第二个矩阵中的第 $ r $ 列挨个相乘,即第一个乘第一个,第二个乘第二个。

接下来是代码时间:

struct Matrix {

    int n, m;

    int a[105][105];

    Matrix () {

        n = m = 0;

        memset (a, 0, sizeof (a));

    }

} ;

Matrix operator * (const Matrix &a, const Matrix &b) {

    Matrix ans;

    int x = a.n, y = b.m;

    for (register int i = 1; i <= x; ++ i)

        for (register int j = 1; j <= y; ++ j)

            for (register int k = 1; k <= a.m; ++ k)

                ans.a[i][j] += a.a[i][k] * b.a[k][j];

    return ans;

}

这里我们交换 for 循环的顺序卡常,我们会发现,枚举顺序是 ikj 的时候运行速度最快, 所以代码如下:

struct Matrix {

    int n, m;

    int a[105][105];

    Matrix () {

        n = m = 0;

        memset (a, 0, sizeof (a));

    }

} ;

Matrix operator * (const Matrix &a, const Matrix &b) {

    Matrix ans;

    int x = a.n, y = b.m;

    for (register int i = 1; i <= x; ++ i)

        for (register int k = 1; k <= a.m; ++ k)

            for (register int j = 1; j <= y; ++ j)

                ans.a[i][j] += a.a[i][k] * b.a[k][j];

    return ans;

}

矩阵快速幂

这个只需要把快速幂里的 int 类型改成 Matrix 类型就行

代码:

struct Matrix {

    int n, m;

    int a[105][105];

    Matrix () {

        n = m = 0;

        memset (a, 0, sizeof (a));

    }

} ;

Matrix operator * (const Matrix &a, const Matrix &b) {

    Matrix ans;

    int x = a.n, y = b.m;

    for (register int i = 1; i <= x; ++ i)

        for (register int k = 1; k <= a.m; ++ k)

            for (register int j = 1; j <= y; ++ j)

                ans.a[i][j] += a.a[i][k] * b.a[k][j];

    return ans;

}

inline Matrix qpow (Matrix x, int y) {

    if (y == 1) return x;

    Matrix tmp = qpow (x, y / 2);

    if (y & 1) return tmp * tmp * x;

    else return tmp * tmp;

}

矩阵快速幂优化递推

P1962

考虑暴力会 TLE,我们使用矩阵快速幂优化

这里我们可以推出:

\[\left[\begin{array}{l} F_{n - 1} & F_{n - 2} \end{array}\right] \times \left[\begin{array}{l} 1 & 1 \\ 1 & 0 \end{array}\right] = \left[\begin{array}{l} F_n & F_{n - 1} \end{array}\right] \]

所以:

\[\left[\begin{array}{l} F_{n + 1} & F_n \end{array}\right] = \left[\begin{array}{l} F_2 & F_1 \end{array}\right] \times \left[\begin{array}{l} 1 & 1 \\ 1 & 0 \end{array}\right]^{n - 1} \]

然后矩阵快速幂就行了

代码:

# include <bits/stdc++.h>

# define int long long

using namespace std;

int n;

int mod = 1000000000 + 7;

struct Matrix {

    int a[15][15];

    Matrix () { for (int i = 1; i <= 2; ++ i) for (int j = 1; j <= 2; ++ j) a[i][j] = 0; }

    Matrix operator * (Matrix y) {

        Matrix res;

        for (int i = 1; i <= 2; ++ i) for (int j = 1; j <= 2; ++ j) for (int k = 1; k <= 2; ++ k) res.a[i][j] = (res.a[i][j] + a[i][k] * y.a[k][j] % mod) % mod;

        return res;

    }

} ;

inline int syzakioi (int n) {

    Matrix res, base;

    base.a[1][1] = base.a[1][2] = base.a[2][1] = 1;

    res.a[1][1] = res.a[1][2] = 1;

    while (n) {

        if (n & 1) res = res * base;

        base = base * base;

        n >>= 1;

    }

    return res.a[1][1];

}

signed main () {

    int n;

    cin >> n;

    if (n <= 2) { puts ("1"); return 0; }

    printf ("%d", syzakioi (n - 2));

    return 0;

}

P1939

使用同样的方式推出矩阵,然后解决,是一样的

最终的矩阵是:

\[\left[\begin{array}{l} F_{n + 1} & F_n & F_{n - 1} \end{array}\right] = \left[\begin{array}{l} F_3 & F_2 & F_1 \end{array}\right] \times \left[\begin{array}{l} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{array}\right]^{n - 2} \]

代码:

# include <bits/stdc++.h>

# define int long long

using namespace std;

int n;

int mod = 1000000000 + 7;

struct Matrix {

    int a[15][15];

    Matrix () { for (int i = 1; i <= 3; ++ i) for (int j = 1; j <= 3; ++ j) a[i][j] = 0; }

    Matrix operator * (Matrix y) {

        Matrix res;

        for (int i = 1; i <= 3; ++ i) for (int j = 1; j <= 3; ++ j) for (int k = 1; k <= 3; ++ k) res.a[i][j] = (res.a[i][j] + a[i][k] * y.a[k][j] % mod) % mod;

        return res;

    }

} ;

inline int syzakioi (int n) {

    Matrix res, base;

    base.a[1][1] = base.a[1][2] = base.a[2][3] = base.a[3][1] = 1;

    res.a[1][1] = res.a[1][2] = res.a[1][3] = 1;

    while (n) {

        if (n & 1) res = res * base;

        base = base * base;

        n >>= 1;

    }

    return res.a[1][1];

}

signed main () {

    int T;

    cin >> T;

    while (T --) {

        int n;

        cin >> n;

        if (n <= 3) { puts ("1"); continue; }

        printf ("%d\n", syzakioi (n - 3));

    }

    return 0;

}

口糊题

求 $ 1 $ 到 $ n $ 经过 $ k $ 条边的方案数

首先我们考虑 dp

$ dp_{i,j} $ 表示当前经过 $ i $ 条边,并且在 $ j $ 点上

然后转移是 $ dp_{i,j} = \sum^n_{x = 1}dp_{i - 1,x} \times z_{x,j} $

这里 $ z_{i,j} $ 表示点 $ i $ 和点 $ j $ 之间是否有边

然后添加一维,滚掉 $ i $ 那一维,变成 $ dp_{d,j} = \sum^n_{z = 1}dp_{d,x} \times z_{x,j} $

这不就是矩阵乘法吗?所以我们直接得出:

$ dp_x = dp_{x - 1} \times z $

然后就是:

$ dp_x = dp_0 \times z^{x} $

然后就切了

P4159

我们考虑拆边,比如边权是 $ 3 $,就拆成 $ 3 $ 条边

这样拆出的点太多,会爆空间

然后我们只需要把一个点延伸出的边中把重复的边合并,即可

然后转换成上面的题

这里我们需要注意一下拆边的方式,首先我们记 $ id(i, j) $ 表示点 $ i $ 拆出的第 $ j + 1 $ 个点,由于最多拆出 $ 9 $ 个点,所以我们考虑 $ id(i, j) = (i - 1) \times 9 + j $

然后我们考虑拆成一条链,也就是拆出的第 $ j + 1 $ 个点和第 $ j $ 个点的距离是 $ 1 $,所以我们就考虑初始化 $ F[id(i, j)][id(i, j - 1)] = 1 $

然后我们考虑增加点,如果点 $ i $ 到点 $ j $ 的距离是 $ x $,那么我们考虑连接点 $ i $ 和点 $ j $ 拆出来的第 $ x - 1 $ 个点,这样保证点 $ i $ 到点 $ j $ 的距离是 $ x $

代码:

# include <bits/stdc++.h>

# define mod 2009

using namespace std;

int n, t;

inline int id (int i, int j) {

    return (i - 1) * 9 + j;

}

struct Matrix {

    int a[105][105];

    Matrix () {

        memset (a, 0, sizeof (a));

    }

    inline void set () {

        for (int i = 0; i < 9 * n; ++ i) a[i][i] = 1;

    }

} F ;

Matrix operator * (const Matrix &a, const Matrix &b) {

    Matrix ans;

    for (register int i = 0; i < 9 * n; ++ i)

        for (register int k = 0; k < 9 * n; ++ k)

            for (register int j = 0; j < 9 * n; ++ j)

                ans.a[i][j] = (ans.a[i][j] + a.a[i][k] * b.a[k][j] % mod) % mod;

    return ans;

}

inline Matrix qpow (Matrix x, int y) {

    Matrix ans;

    ans.set ();

    while (y) {

        if (y & 1) ans = ans * x;

        x = x * x;

        y >>= 1;

    }

    return ans;

}

signed main () {

    cin >> n >> t;

    for (int i = 1; i <= n; ++ i) {

        for (int j = 1; j <= 8; ++ j) F.a[id (i, j)][id (i, j - 1)] = 1;

        for (int j = 1; j <= n; ++ j) {

            int x;

            scanf ("%1d", &x);

            if (x) F.a[id (i, 0)][id (j, x - 1)] = 1;

        }

    }

    F = qpow (F, t);

    cout << F.a[id (1, 0)][id (n, 0)] << endl;

    return 0;

}

五、素数

素数就是只有 $ 1 $ 和它本身因数的数,所以可得,$ 0 $ 和 $ 1 $ 不是素数

判定

枚举因数,这里有个神奇的性质,也就是判断的时候只需要枚举到 $ \sqrt{x} $

inline bool Prime_checking (int x) {

    if (x == 1 || x == 0) return 0;

    for (register int i = 2; i * i <= x; ++ i) if (! (x % i)) return 0;

    return 1;
}

这里讲一种错误写法,i <= sqrt (x),这样每循环一次都要计算 sqrt(x),所以我们可以提前算好

这有些类似 strlen 函数,这个函数是 $ O(n) $ 的

上述代码不需要考虑精度

分解质因数

直接枚举所有因数,如果发现当前数可以被整除,就知道一定是因数,然后就可以了,代码:

int prime[114514], num[114514], cnt = 0;
inline void Factorize (int x) {

    for (register int i = 2; i * i <= x; ++ i) {

        if (! (x % i)) {

            ++ cnt;

            prime[cnt] = i;

            while (! (x % i)) ++ num[cnt], x /= i;

        }

    }

    if (x != 1) {

        prime[++ cnt] = x;

        num[cnt] = 1;

    }

}

CF45G

这个题很简单,考虑三种情况:

  1. 所有的数之和是质数,只需要全输出 1

  2. 是大于 $ 4 $ 的偶数,只需要考虑如何分解成两个质数

  3. 如果是奇数,就考虑如果 $ - 2 $ 是质数,就分解,否则就 $ - 3 $,剩下的一定是偶数,考虑第二种情况

代码:

# include <bits/stdc++.h>

using namespace std;

int ans[114514];

inline bool Prime (int x) {

    if (x == 1 || x == 0) return 0;

    for (register int i = 2; i * i <= x; ++ i) if (! (x % i)) return 0;

    return 1;
}

signed main () {

    int m;

    cin >> m;

    int n = (m + 1) * m / 2;

    if (Prime (n)) {

        for (register int i = 1; i <= m; ++ i) cout << 1 << " ";

        cout << endl;

        return 0;

    }

    for (register int i = 1; i <= m; ++ i) ans[i] = 1;

    if (n & 1 && ! Prime (n - 2)) ans[3] = 3, n -= 3;

    for (register int i = 2; i <= m; ++ i) {

        if (Prime (i) && Prime (n - i)) {

            ans[i] = 2;

            break;

        }

    }

    for (register int i = 1; i <= m; ++ i) cout << ans[i] << " ";

    cout << endl;

    return 0;

}
posted @ 2023-04-29 14:51  __Tzf  阅读(118)  评论(6编辑  收藏  举报