2025.5.10-5.20 学习随记(数学)

1.筛质数

感觉不是很难,稍微想想应该可以。

2.分解质因数

分解质因数的运用。

3.快速幂

4.约数个数

5.欧拉函数

公式是 phi(n)=n(1-1/p1)(1-1/p2)...(1-1/pk),

p1~pk 为 n 的质因数。

然后可以通过筛法求 1~n 每个数的欧拉函数。

6.同余和 CRT

同余方程一般形式为 ax+by=1,可通过 exgcd 求得一组通解。

当然也有形似 ax+by=c 的方程,此时若是 (a,b) 不整除 c 则方程无解。

然后是 CRT。

结论是 x=aiM/miinv(M/mi),其中 M 表示所有 mi 的乘积,inv 为逆元,所以这个东西又要 exgcd。

7.矩阵乘法

优化 DP 的一种策略。

一般系数放第二个矩阵,变量放第一个矩阵。

8.组合计数

主要就是卡特兰数和 Lucas 定理。

卡特兰数的题目一般是要看题目中能不能转化为前缀中...>... 的形式。

然后 Lucas 定理就是 Cn,m%p=Cn%p,m%p*Cn/p,m/p。

P3200 [HNOI2009] 有趣的数列(卡特兰数)

比较巧妙吧,但是感觉不是很难想到。

就是当你对于一个数想要放在偶数位时,必须保证他前面放奇数位的数个数必须大于放偶数位的数个数。

然后就转化为满足前缀中 ...>... 的形式了,直接卡特兰数即可。

不过有一个比较需要注意就是要用分解质因数 + qp 做,不然会 T 飞。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 2000010;

int n, p;
bool st[N];
int primes[N], cnt;

void init()
{
    st[1] = true;
    for (int i = 2; i < N; i ++ )
    {
        if (!st[i]) primes[ ++ cnt] =i;
        for (int j = 1; primes[j] * i < N; j ++ )
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int get(int n, int x)
{
    int s = 0;
    while (n)
    {
        s += n / x;
        n /= x;
    }
    
    return s;
}

int quick_power(int a, int b)
{
    int res = 1;
    while (b)
    {
        if (b & 1) (res *= a) %= p;
        (a *= a) %= p;
        b >>= 1;
    }
    
    return res;
}

int C(int n, int m)
{
    if (n < m) return 0;
    
    int res = 1;
    for (int i = 1; i <= cnt; i ++ )
    {
        int num = get(n, primes[i]) - get(m, primes[i]) - get(n - m, primes[i]);
        
        (res *= quick_power(primes[i], num)) %= p;
    }
    
    return res;
}

signed main()
{
    cin >> n >> p;
    
    init();
    
    cout << ((C(n * 2, n) - C(n * 2, n - 1)) % p + p) % p << '\n';
    
    return 0;
}

9.高斯消元

先找系数最大的一行,避免精度误差过大。

然后把这一行系数变为 1,再消去剩下的方程组中本项系数。

球形空间产生器(Gauss消元)

#include <bits/stdc++.h>


using namespace std;

const int N = 20;
const double eps = 1e-9;

int n;
double a[N][N], b[N][N];

void gauss()
{
    for (int r = 1, c = 1; c <= n; c ++ )
    {
        int p = r;
        for (int i = r + 1; i <= n; i ++ )
            if (fabs(b[i][c]) > fabs(b[p][c])) p = i;
        
        if (fabs(b[p][c]) < eps) continue;
        
        for (int i = c; i <= n + 1; i ++ ) swap(b[r][i], b[p][i]);
        for (int i = n + 1; i >= c; i -- ) b[r][i] /= b[r][c];
        for (int i = r + 1; i <= n; i ++ )
            if (fabs(b[i][c]) > eps)
                for (int j = n + 1; j >= c; j -- )
                    b[i][j] -= b[r][j] * b[i][c];
        
        r ++ ;
    }
    
    // if (r < n)
    // {
    //     for (int i = r; i <= n; i ++ )
    //         if (fabs(b[i][n + 1]) > eps)
    //             return 2;
    //     return 1;
    // }
    
    for (int i = n; i > 1; i -- )
        for (int j = i - 1; j; j -- )
        {
            b[j][n + 1] -= b[i][n + 1] * b[j][i];
            b[j][i] = 0;
        }
}

signed main()
{
    cin >> n;
    for (int i = 1; i <= n + 1; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> a[i][j];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            b[i][j] = 2 * (a[i + 1][j] - a[1][j]),
            b[i][n + 1] += a[i + 1][j] * a[i + 1][j] - a[1][j] * a[1][j];
    
    gauss();
    
    for (int i = 1; i <= n; i ++ ) printf("%.3lf ", b[i][n + 1]);
    cout << '\n';
    
    return 0;
}

开关问题

#include <bits/stdc++.h>

using namespace std;

const int N = 35;

int n;
int a[N][N];

int gauss()
{
    int r, c;
    for (r = 1, c = 1; c <= n; c ++ )
    {
        int t = r;
        for (int i = r + 1; i <= n; i ++ )
            if (a[i][c]) t = i;
        
        if (!a[t][c]) continue;
        
        for (int i = c; i <= n + 1; i ++ ) swap(a[t][i], a[r][i]);
        for (int i = r + 1; i <= n; i ++ )
            for (int j = n + 1; j >= c; j -- )
                a[i][j] ^= a[i][c] & a[r][j];
        
        r ++ ;
    }
    
    if (r <= n)
    {
        int res = 1;
        for (int i = r; i <= n; i ++ )
        {
            if (a[i][n + 1]) return -1;
            res *= 2;
        }
        return res;
    }
    
    return 1;
}

signed main()
{
    int T;
    cin >> T;
    while (T -- )
    {
        memset(a, 0, sizeof a);
        cin >> n;
        for (int i = 1; i <= n; i ++ ) cin >> a[i][n + 1];
        
        for (int i = 1; i <= n; i ++ )
        {
            int x;
            cin >> x;
            a[i][n + 1] ^= x;
            a[i][i] = 1;
        }
        
        int x, y;
        while (cin >> x >> y, x || y) a[y][x] = 1;
        
        int t = gauss();
        
        if (t == -1) puts("Oh,it's impossible~!!");
        else cout << t << '\n';
    }
    
    return 0;
}

10.容斥原理

值得一提的是 Mobius 函数。

莫比乌斯函数就是对于 n,若分解质因数后,每个质因数有多个,则函数值为 0;

反之若质因数个数为奇数,则为 1;若质因数个数为偶数,则为 -1。刚好对应了容斥的奇减偶加。

可以比较巧妙的结合入题目。

CF451E Devu and Flowers

容斥的时候,考虑一个限制不满足之后,就相当于变为一个子问题:在 n 个盒子中继续取 m-ai-1。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 110, mod = 1e9 + 7;

int n, m;
int a[N], inv[N];

int quick_power(int a, int b)
{
    int res = 1;
    
    while (b)
    {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    
    return res;
}

int C(int n, int m)
{
    if (n < m) return 0;
    int res = 1;
    for (int i = n - m + 1; i <= n; i ++ ) (res *= i % mod) %= mod;
    for (int i = 1; i <= m; i ++ ) (res *= inv[i]) %= mod;
    
    return res;
}

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    
    for (int i = 1; i <= n; i ++ ) inv[i] = quick_power(i, mod - 2);
    int res = 0;
    for (int i = 0; i < (1 << n); i ++ )
    {
        int sum = m, cnt = 0;
        for (int j = 1; j <= n; j ++ )
        {
            if (!(i >> (j - 1) & 1)) continue;
            sum -= a[j] + 1;
            cnt ++ ;
        }
        if (cnt & 1) (res = (res - C(sum - 1 + n, n - 1)) % mod + mod) %= mod;
        else (res += C(sum - 1 + n, n - 1)) %= mod;
    }
    
    cout << res << '\n';
    
    return 0;
}

P3455 [POI 2007] ZAP-Queries(破译密码)

莫比乌斯函数和容斥的题目关联的可能比较多吧。

莫比乌斯函数就是对于 n,若分解质因数后,每个质因数有多个,则函数值为 0;

反之若质因数个数为奇数,则为 1;若质因数个数为偶数,则为 -1。刚好对应了容斥的奇减偶加。

然后莫比乌斯函数是一个积性函数,可以在线性筛质数的时候求。

对于这题,先转化为求 gcd(x/d,y/d)=1 的 (x,y) 对数。

然后就是总对数减去公约数一个的对数,加上公约数两个的对数……

于是前面的系数为莫比乌斯函数,考虑将所有数分段,因为最终 floor(a/i) 取值只有 2sqrt(a) 种。

这个很好证,前 sqrt(a) 个数最多有 sqrt(a) 个,后 sqrt(a) 个数取值最多 sqrt(a) 个。

主要瓶颈在于求每段的右端点和证明。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 50010;

int n;
int primes[N], cnt;
int mobius[N];
bool st[N];

void init(int n)
{
    mobius[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
             primes[ ++ cnt] = i;
             mobius[i] = -1;
        }
        for (int j = 1; primes[j] * i <= n; j ++ )
        {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0)
            {
                mobius[i * primes[j]] = 0;
                break;
            }
            mobius[i * primes[j]] = -mobius[i];
        }
    }
    
    for (int i = 1; i <= n; i ++ ) mobius[i] += mobius[i - 1];
}

signed main()
{
    init(N - 1);
    cin >> n;
    while (n -- )
    {
        int a, b, d;
        scanf("%lld%lld%lld", &a, &b, &d);
        
        int res = 0;
        a /= d, b /= d;
        for (int l = 1, r; l <= min(a, b); l = r + 1)
        {
            r = min(min(a, b), min(a / (a / l), b / (b / l)));
            res += (mobius[r] - mobius[l - 1]) * (a / l) * (b / l);
        }
        
        cout << res << '\n';
    }
    
    return 0;
}

11.概率与数学期望

\(E(j)=E(i)*P(i)+W(i->j)\)

12.博弈论

比较著名的看了下,比较杂、多,不写了。

P2252 [SHOI2002] 取石子游戏/【模板】威佐夫博弈

#include <bits/stdc++.h>
#define int long long

using namespace std;

signed main()
{
    int a, b;
    cin >> a >> b;
    if (a > b) swap(a, b);
    if ((int)((sqrt(5) + 1.0) / 2.0 * (b - a)) == a) cout << "0\n";
    else cout << "1\n";

    return 0;
}

P2599 [ZJOI2009] 取石子游戏

博弈论的题是不是都是神仙题……

设 l[i][j] 表示区间 [i,j] 左边放数量为 l[i][j] 的石子堆可以使得先手必败。

相对的,设 r[i][j] 表示区间 [i,j] 右边放数量为 r[i][j] 的石子堆可以使得先手必败。

不难发现这有性质:l,r 一定是唯一确定的。

根据 SG 函数来看,若存在两个取值 X1<X2为必败态,则对于 X2 来说可以转化到 X1 的情况,那么 X2 应是必胜态。

考虑递推,大力分讨。

只讨论左边的更新即可,右边对称。

设 L 表示 l[i][j-1],R 表示 r[i][j-1],X 表示 a[j]。

1.X=R

则区间 [i,j] 已为必败态,在左边不需要放,l[i][j] 取 0。

2.X<R,X<L

无论怎么取,X' 都一定小于 L,R。

因为必败态的取值唯一,所以我们只要保证当先手将左右一堆石子取空时,另一堆仍有即可,那么留给后手的就是必胜态。

故直接填 X 即可。

3.R<L,R<X<=L

设 Y 表示 l[i][j]。

考虑先手取的是左边还是右边。

如果是左边的话,当取完数量仍在 [R,L) 间,后手就保持原来的相对关系;

当取完数量小于 R,那么后手就立即把另一堆取成与左边相同,情况就变成 2 中的必败态了。

如果是右边的话,显然不能取到 R,若取完大于 R,则保持相对关系不变;若取完小于 R,则取成与 R 相等,同 2。

最终结论是填 X-1 即可。

4.L<R,L<X<=R

同 3 类似,结论是填 X+1。

5.X>L,X>R

同 2 类似,结论是填 X。

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, a[N];
int l[N][N], r[N][N];

signed main()
{
    int T;
    cin >> T;
    while (T -- )
    {
        cin >> n;
        for (int i = 1; i <= n; i ++ ) cin >> a[i];
        
        for (int len = 1; len <= n; len ++ )
        {
            for (int i = 1; i + len - 1 <= n; i ++ )
            {
                int j = i + len - 1, L, R, X;
                L = l[i][j - 1], R = r[i][j - 1], X = a[j];
                if (X == R) l[i][j] = 0;
                else if (X < min(L, R) || X > max(L, R)) l[i][j] = X;
                else if (L < R) l[i][j] = X + 1;
                else l[i][j] = X - 1;
                
                L = l[i + 1][j], R = r[i + 1][j], X = a[i];
                if (X == L) r[i][j] = 0;
                else if (X < min(L, R) || X > max(L, R)) r[i][j] = X;
                else if (L > R) r[i][j] = X + 1;
                else r[i][j] = X - 1;
            }
        }
        
        if (n == 1) cout << "1\n";
        else cout << (l[2][n] != a[1]) << '\n';
    }
    
    return 0;
}
posted @ 2025-05-20 21:20  MafuyuQWQ  阅读(35)  评论(0)    收藏  举报