题目链接
题目描述
设一个正整数序列 的前缀和为
。若对于所有的
,
都是完全平方数,则称该序列为一条前缀平方序列。
给定两个正整数 与
,请你计算满足以下两个条件的前缀平方序列的数量:
-
序列长度为
。
-
对任意前缀和
均有
。
结果对 取模。
解题思路
这是一个计数问题,我们可以通过分析前缀平方序列的结构,将其转化为一个经典的组合数学问题。
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
和组合数计算都是。总时间复杂度由预处理主导,为
。
-
空间复杂度:需要两个数组存储阶乘和逆元,大小为
。因此,总空间复杂度为
。