题目链接

前缀平方和序列

题目描述

设一个正整数序列 的前缀和为 。若对于所有的 都是完全平方数,则称该序列为一条前缀平方序列。

给定两个正整数 ,请你计算满足以下两个条件的前缀平方序列的数量:

  1. 序列长度为

  2. 对任意前缀和 均有

结果对 取模。

解题思路

这是一个计数问题,我们可以通过分析前缀平方序列的结构,将其转化为一个经典的组合数学问题。

1. 结构分析

根据定义,序列 的前缀和 都是完全平方数。

我们可以设 ,其中 是一个正整数。

  • ...

由于序列中的 都是正整数,所以对于任意 ,都有

这意味着

代入我们设定的变量,即 。因为 都是正整数,所以这等价于

由此,我们得到一个关键结论:序列 是一个严格单调递增的正整数序列。

2. 约束转化

题目给出的另一个约束是 对所有 都成立。

由于 是一个递增序列,这个约束等价于其最大项

,这等价于

3. 问题重构

现在,原问题被转化为了: 计算有多少个长度为 的严格递增正整数序列 ,其最大项 不超过

这个问题可以进一步简化。我们只需要从整数集合 中选出 个不同的数。一旦选定了这 个数,只有一种方式可以将它们排列成一个严格递增的序列(即从小到大排列)。

因此,问题的核心就是:从 个数中选出 个数的组合数。

,则答案为

4. 组合数计算

我们需要计算

组合数公式为

在模运算下,除法需要用乘法逆元来计算。因为模数 是一个质数,我们可以使用费马小定理来求逆元,即

计算步骤如下:

  1. 计算

  2. 如果 ,则无法选出 个数,方案数为

  3. 预处理阶乘及其逆元。由于 的范围可达 ,而 最大约为 ,我们需要预处理到

  4. 利用公式 计算结果。

代码

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

const int MOD = 1000000007;
const int MAX_N = 100001;

// 用于存储阶乘和阶乘的逆元
vector<long long> fact(MAX_N);
vector<long long> invFact(MAX_N);

// 快速幂函数,用于计算 x^y % mod
long long power(long long base, long long exp) {
    long long res = 1;
    base %= MOD;
    while (exp > 0) {
        if (exp % 2 == 1) res = (res * base) % MOD;
        base = (base * base) % MOD;
        exp /= 2;
    }
    return res;
}

// 模块化逆元函数
long long modInverse(long long n) {
    return power(n, MOD - 2);
}

// 预计算阶乘和阶乘的逆元
void precompute() {
    fact[0] = 1;
    invFact[0] = 1;
    for (int i = 1; i < MAX_N; i++) {
        fact[i] = (fact[i - 1] * i) % MOD;
        invFact[i] = modInverse(fact[i]);
    }
}

// 组合数计算函数
long long nCr_mod_p(int n, int r) {
    if (r < 0 || r > n) {
        return 0;
    }
    return (((fact[n] * invFact[r]) % MOD) * invFact[n - r]) % MOD;
}

int main() {
    precompute();
    long long n, m;
    cin >> n >> m;
    
    long long limit = static_cast<long long>(sqrt(m));
    
    cout << nCr_mod_p(limit, n) << endl;
    
    return 0;
}
import java.util.Scanner;
import java.lang.Math;

public class Main {
    static final int MOD = 1000000007;
    static final int MAX_N = 100001;
    static long[] fact = new long[MAX_N];
    static long[] invFact = new long[MAX_N];

    // 快速幂函数
    static long power(long base, long exp) {
        long res = 1;
        base %= MOD;
        while (exp > 0) {
            if (exp % 2 == 1) res = (res * base) % MOD;
            base = (base * base) % MOD;
            exp /= 2;
        }
        return res;
    }

    // 模块化逆元函数
    static long modInverse(long n) {
        return power(n, MOD - 2);
    }

    // 预计算阶乘和阶乘的逆元
    static void precompute() {
        fact[0] = 1;
        invFact[0] = 1;
        for (int i = 1; i < MAX_N; i++) {
            fact[i] = (fact[i - 1] * i) % MOD;
            invFact[i] = modInverse(fact[i]);
        }
    }

    // 组合数计算函数
    static long nCr_mod_p(int n, int r) {
        if (r < 0 || r > n) {
            return 0;
        }
        return (((fact[n] * invFact[r]) % MOD) * invFact[n - r]) % MOD;
    }

    public static void main(String[] args) {
        precompute();
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long m = sc.nextLong();
        
        long limit = (long) Math.sqrt(m);
        
        System.out.println(nCr_mod_p((int) limit, n));
    }
}
import math

MOD = 1000000007
MAX_N = 100001

# 预计算阶乘和阶乘的逆元
fact = [1] * MAX_N
invFact = [1] * MAX_N

for i in range(1, MAX_N):
    fact[i] = (fact[i - 1] * i) % MOD

invFact[MAX_N - 1] = pow(fact[MAX_N - 1], MOD - 2, MOD)
for i in range(MAX_N - 2, -1, -1):
    invFact[i] = (invFact[i + 1] * (i + 1)) % MOD

# 组合数计算函数
def nCr_mod_p(n, r):
    if r < 0 or r > n:
        return 0
    numerator = fact[n]
    denominator = (invFact[r] * invFact[n - r]) % MOD
    return (numerator * denominator) % MOD

# 读取输入
n, m = map(int, input().split())

limit = int(math.sqrt(m))

# 计算并输出结果
print(nCr_mod_p(limit, n))

算法及复杂度

  • 算法:数论、组合数学

  • 时间复杂度:预处理阶乘和逆元的时间复杂度为 (若每次单独求逆元)或 (若线性求逆元),其中 是预处理的范围(), 是模数。查询阶段,sqrt 和组合数计算都是 。总时间复杂度由预处理主导,为

  • 空间复杂度:需要两个数组存储阶乘和逆元,大小为 。因此,总空间复杂度为