题解

本文档包含了六个算法问题的详细题解,涵盖组合数学、数论、容斥原理、素数筛选、快速幂等核心算法。


题目概览

题号 题目名称 核心算法
A 计算系数 二项式定理 + 组合数预处理 + 快速幂
B 平方和 数学公式 + 费马小定理求逆元
C Co-prime 质因数分解 + 容斥原理
D Prime Cuts 欧拉筛 + 区间裁剪
E Pseudoprime numbers 素数判定 + 快速幂
F Sequence 组合数递推 + 卷积思想

Problem A: 计算系数

题意简述

给定多项式 (ax+by)k(ax + by)^k(ax+by)k,请求出展开后 xnymx^n y^mxnym 项的系数。其中 a,b,k,n,ma, b, k, n, ma,b,k,n,m 均为整数,结果对 100071000710007 取模。

核心思路

根据二项式定理(ax+by)k(ax + by)^k(ax+by)k 的展开通项为:

Tn=(kn)(ax)n(by)k−n=(kn)anbk−nxnyk−n T_{n} = \binom{k}{n} (ax)^n (by)^{k-n} = \binom{k}{n} a^n b^{k-n} x^n y^{k-n} Tn=(nk)(ax)n(by)kn=(nk)anbknxnykn

题目保证 n+m=kn + m = kn+m=k,因此 xnymx^n y^mxnym 项的系数为:

(kn)⋅an⋅bm mod 10007 \boxed{\binom{k}{n} \cdot a^n \cdot b^m \bmod 10007} (nk)anbmmod10007

代码要点

const int N = 1005;
const int mod = 10007;
int C[N][N];

// 预处理组合数(杨辉三角)
void getC() {
    for (int i = 0; i < N; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++) {
            C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
        }
    }
}

// 快速幂
int fastpower(int base, int power) {
    int res = 1;
    while (power) {
        if (power & 1) res = res * base % mod;
        base = base * base % mod;
        power >>= 1;
    }
    return res;
}

// 主函数计算
int ans = C[k][n] * fastpower(a, n) % mod * fastpower(b, m) % mod;

复杂度分析

  • 预处理组合数:O(N2)O(N^2)O(N2),其中 N=1005N = 1005N=1005
  • 单次查询:O(log⁡n+log⁡m)O(\log n + \log m)O(logn+logm)
  • 空间复杂度:O(N2)O(N^2)O(N2)

Problem B: 平方和

题意简述

给定 nnn,求:

∑i=1ni2=12+22+⋯+n2(mod109+7) \sum_{i=1}^{n} i^2 = 1^2 + 2^2 + \cdots + n^2 \pmod{10^9+7} i=1ni2=12+22++n2(mod109+7)

核心思路

使用平方和公式

∑i=1ni2=n(n+1)(2n+1)6 \sum_{i=1}^{n} i^2 = \frac{n(n+1)(2n+1)}{6} i=1ni2=6n(n+1)(2n+1)

由于模数 109+710^9+7109+7 是质数,且 666 与模数互质,可以用费马小定理666 的逆元:

6−1≡6109+5(mod109+7) 6^{-1} \equiv 6^{10^9+5} \pmod{10^9+7} 616109+5(mod109+7)

代码实现

#include <iostream>
using namespace std;
#define int long long
const int mod = 1000000007;

int fastpower(int base, int power) {
    int res = 1;
    while (power) {
        if (power & 1) res = res * base % mod;
        base = base * base % mod;
        power >>= 1;
    }
    return res;
}

signed main() {
    int n;
    cin >> n;
    int inv6 = fastpower(6, mod - 2);  // 费马小定理求逆元
    int ans = n % mod * ((n + 1) % mod) % mod * ((2 * n + 1) % mod) % mod * inv6 % mod;
    cout << ans << endl;
    return 0;
}

复杂度分析

  • 时间复杂度:O(log⁡mod)O(\log mod)O(logmod),仅需一次快速幂
  • 空间复杂度:O(1)O(1)O(1)

Problem C: Co-prime

题意简述

给定数 NNN,统计闭区间 [A,B][A, B][A,B] 中有多少个整数与 NNN 互质。

核心思路

互质的定义:gcd⁡(x,N)=1\gcd(x, N) = 1gcd(x,N)=1

区间统计通常转化为前缀和:

Ans=f(B)−f(A−1) \text{Ans} = f(B) - f(A-1) Ans=f(B)f(A1)

其中 f(x)f(x)f(x) 表示 [1,x][1, x][1,x] 中与 NNN 互质的数的个数。

容斥原理:用 NNN 的所有不同质因子,计算 [1,x][1, x][1,x] 中能被这些质因子整除的数的个数,然后用总数减去。

NNN 的不同质因子集合为 {p1,p2,…,pk}\{p_1, p_2, \dots, p_k\}{p1,p2,,pk},则:

f(x)=x−∑i⌊xpi⌋+∑i<j⌊xpipj⌋−⋯ f(x) = x - \sum_{i} \left\lfloor \frac{x}{p_i} \right\rfloor + \sum_{i<j} \left\lfloor \frac{x}{p_i p_j} \right\rfloor - \cdots f(x)=xipix+i<jpipjx

代码实现

ll calc(ll x, vector<int>& p) {
    int sz = p.size();
    ll ans = 0;
    // 枚举所有子集
    for (int mask = 1; mask < (1 << sz); mask++) {
        ll t = 1;
        int bits = 0;
        for (int i = 0; i < sz; i++) {
            if (mask & (1 << i)) {
                if (t * p[i] > x) { t = 0; break; }
                t *= p[i];
                bits++;
            }
        }
        if (t) {
            if (bits & 1) ans += x / t;   // 奇数个因子,加
            else ans -= x / t;            // 偶数个因子,减
        }
    }
    return x - ans;  // 总数减去不互质的个数
}

复杂度分析

  • 质因数分解:O(N)O(\sqrt{N})O(N )
  • 容斥计算:O(2k)O(2^k)O(2k),其中 kkk 为不同质因子个数(N≤109N \le 10^9N109k≤9k \le 9k9
  • 时间复杂度可接受

Problem D: Prime Cuts

题意简述

111NNN 之间的质数列表(注意:本题把 111 也视为质数)中,裁剪出中间若干个质数并输出。

若质数总数为偶数,则输出中间 2c2c2c 个;若为奇数,则输出中间 2c−12c-12c1 个。

核心思路

  1. 使用欧拉线性筛筛选出 [1,N][1, N][1,N] 内的所有素数(包含 111
  2. 根据 szszsz(素数个数)的奇偶性确定要输出的个数:
    • 奇数:cnt=2c−1cnt = 2c - 1cnt=2c1
    • 偶数:cnt=2ccnt = 2ccnt=2c
  3. cnt≥szcnt \ge szcntsz,则输出全部素数
  4. 否则,从中间位置开始截取 cntcntcnt

代码实现

void get_prime() {
    primes.clear();
    memset(vis, false, sizeof vis);
    primes.push_back(1);          // 题目特殊要求
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) primes.push_back(i);
        for (int j = 1; j < primes.size() && 1LL * i * primes[j] <= n; j++) {
            vis[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

// 主逻辑
int sz = primes.size();
if (sz & 1) c = 2 * c - 1;
else c <<= 1;

if (c >= sz) {
    // 输出全部
} else {
    int st = (sz - c) >> 1;      // 起始位置
    for (int i = st; i < st + c; i++) cout << " " << primes[i];
}

输出格式注意

每个测试用例输出后需跟两个换行符\n\n)。


Problem E: Pseudoprime numbers

题意简述

给定 2<p≤1092 < p \le 10^92<p1091<a<p1 < a < p1<a<p,判断 ppp 是否是以 aaa 为底的伪素数

伪素数定义:ppp 本身是合数,但满足 ap≡a(modp)a^p \equiv a \pmod{p}apa(modp)

核心思路

  1. 先判断 ppp 是否为素数,若为素数则直接输出 no
  2. ppp 为合数,用快速幂计算 ap mod pa^p \bmod papmodp
  3. ap≡a(modp)a^p \equiv a \pmod{p}apa(modp),则为伪素数,输出 yes;否则输出 no

代码实现

bool isprime(ll x) {
    if (x < 2) return false;
    for (ll i = 2; i * i <= x; i++) {
        if (x % i == 0) return false;
    }
    return true;
}

ll fastpower(ll base, ll power, ll mod) {
    ll ans = 1;
    while (power) {
        if (power & 1) ans = ans * base % mod;
        base = base * base % mod;
        power >>= 1;
    }
    return ans;
}

// 判断逻辑
if (isprime(p)) {
    cout << "no\n";
} else {
    if (fastpower(a, p, p) == a) cout << "yes\n";
    else cout << "no\n";
}

注意事项

  • ppp 的范围达 10910^9109O(p)O(\sqrt{p})O(p ) 的素性判断可行(316233162331623 次循环)
  • 不能使用费马小定理的逆命题(因为 ppp 是合数)

Problem F: Sequence

题意简述

给定序列 A(1),A(2),…,A(n)A(1), A(2), \dots, A(n)A(1),A(2),,A(n),定义新序列 BBB

B(i)=A(i)+2A(i−1)+3A(i−2)+⋯+iA(1) B(i) = A(i) + 2A(i-1) + 3A(i-2) + \cdots + iA(1) B(i)=A(i)+2A(i1)+3A(i2)++iA(1)

B(i)=∑j=1i(i−j+1)⋅A(j)B(i) = \sum_{j=1}^{i} (i-j+1) \cdot A(j)B(i)=j=1i(ij+1)A(j)

现在给定 kkk,需要计算:

∑j=1i(k+i−j−1i−j)A(j) \sum_{j=1}^{i} \binom{k + i - j - 1}{i - j} A(j) j=1i(ijk+ij1)A(j)

核心思路

观察递推关系。考虑生成函数或组合恒等式,实际上系数可以通过以下递推计算:

Ki=(k+i−1i)K_i = \binom{k + i - 1}{i}Ki=(ik+i1),则:

Ki=Ki−1⋅k+i−1i K_i = K_{i-1} \cdot \frac{k + i - 1}{i} Ki=Ki1ik+i1

用模逆元处理除法。

最终:

B(i)=∑j=0iA(j)⋅Ki−j B(i) = \sum_{j=0}^{i} A(j) \cdot K_{i-j} B(i)=j=0iA(j)Kij

这是一个卷积的形式,但 n≤300n \le 300n300,可以用 O(n2)O(n^2)O(n2) 直接计算。

代码实现

const int N = 305;
const ll mod = 1e9 + 7;
ll inv[N], karr[N];

void solve() {
    // 预处理逆元
    inv[0] = inv[1] = 1;
    for (int i = 2; i < N; i++) {
        inv[i] = mod - mod / i * inv[mod % i] % mod;
    }
    
    while (cin >> n) {
        for (int i = 0; i < n; i++) cin >> a[i];
        cin >> k;
        
        // 计算组合数系数 K_i = C(k + i - 1, i)
        karr[0] = 1;
        for (int i = 1; i < n; i++) {
            ll tmp = (2 * k + i - 1) % mod;   // 注意递推公式
            karr[i] = tmp * karr[i - 1] % mod * inv[i] % mod;
        }
        
        // 卷积计算
        vector<ll> ans(n, 0);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= i; j++) {
                ans[i] = (ans[i] + a[j] * karr[i - j]) % mod;
            }
        }
        
        // 输出
        for (int i = 0; i < n; i++) {
            if (i) cout << ' ';
            cout << ans[i];
        }
        cout << '\n';
    }
}

数学推导

原式:

B(i)=∑j=1i(k+i−j−1i−j)A(j) B(i) = \sum_{j=1}^{i} \binom{k + i - j - 1}{i - j} A(j) B(i)=j=1i(ijk+ij1)A(j)

t=i−jt = i - jt=ij,则:

B(i)=∑t=0i−1(k+t−1t)A(i−t) B(i) = \sum_{t=0}^{i-1} \binom{k + t - 1}{t} A(i - t) B(i)=t=0i1(tk+t1)A(it)

所以系数序列为 Kt=(k+t−1t)K_t = \binom{k + t - 1}{t}Kt=(tk+t1),满足:

Kt=(k+t−1)!t!(k−1)! K_t = \frac{(k + t - 1)!}{t!(k-1)!} Kt=t!(k1)!(k+t1)!

递推关系:

Kt=Kt−1⋅k+t−1t K_t = K_{t-1} \cdot \frac{k + t - 1}{t} Kt=Kt1tk+t1

使用模逆元实现递推。


总结

题目 核心知识点 易错点
A 二项式定理、组合数 取模运算的顺序
B 数学公式、费马小定理 模逆元的使用
C 容斥原理、质因数分解 区间转换、子集枚举
D 欧拉筛、区间裁剪 包含1、输出格式
E 快速幂、素性判断 费马小定理不可逆用
F 组合数递推、卷积 模运算下的逆元处理

以上六题覆盖了算法竞赛中常见的数论与组合数学题型,建议掌握其核心思想以应对类似问题。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐