题目链接
题目描述
一个正整数序列 被称为 前缀平方序列,如果其所有前缀和
都是完全平方数。
给定两个正整数
和
,请计算满足以下条件的前缀平方序列的数量:
- 序列长度为
。
- 总和
。
结果需要对 取模。
输入:
- 一行输入两个整数
。
输出:
- 输出一个整数,表示满足条件的序列数量模
的值。
解题思路
这道题表面上是关于序列计数,但可以通过分析其内在结构,将其转化为一个经典的组合数学问题。
首先,我们来分析“前缀平方序列”的性质。设序列的前缀和为 。
根据题意,每个
都必须是一个完全平方数。我们可以设
,其中
是一个正整数。
- ...
由于序列 中的每个元素
都必须是正整数,我们可以得到:
(对于
)
从 可以推导出
。因为
都是正数,所以
。
同时,
。
这样,我们就建立了一个从序列
到序列
的一一对应关系。原问题等价于寻找一个满足特定条件的整数序列
。
这个序列 需要满足以下条件:
,即一个长度为
的严格递增正整数序列。
,这意味着
。
所以,问题被转化为了:从集合 中,选出
个不同的数,能组成多少个严格递增的序列?
一旦我们从这个集合中选定了 个不同的数,它们自动就只能以一种方式(从小到大)排列成一个满足条件的严格递增序列
。
因此,问题的核心就变成了从
个数中选出
个数的组合数。
答案就是组合数 。
如果 ,则无法选出
个不同的数,方案数为 0。
否则,我们需要计算
。
由于模数是质数,我们可以使用公式:
我们可以先计算分子
,再计算分母
,然后用费马小定理求出分母的模逆元,最后相乘得到结果。
具体的计算步骤:
- 计算
。
- 如果
,答案为 0。
- 否则,计算分子
num = (K) * (K-1) * ... * (K-n+1) mod P
。 - 计算分母
den = n! mod P
。 - 使用快速幂计算
den
的模逆元inv_den = power(den, P-2, P)
。 - 最终答案为
(num * inv_den) mod P
。
代码
#include <iostream>
#include <cmath>
using namespace std;
using LL = long long;
const int MOD = 1e9 + 7;
LL power(LL base, LL exp) {
LL res = 1;
base %= MOD;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % MOD;
base = (base * base) % MOD;
exp /= 2;
}
return res;
}
LL inverse(LL n) {
return power(n, MOD - 2);
}
LL combinations(LL k, LL n) {
if (n > k) return 0;
if (n == 0 || n == k) return 1;
if (n > k / 2) n = k - n;
LL numerator = 1;
for (LL i = 0; i < n; ++i) {
numerator = (numerator * (k - i)) % MOD;
}
LL denominator = 1;
for (LL i = 1; i <= n; ++i) {
denominator = (denominator * i) % MOD;
}
return (numerator * inverse(denominator)) % MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
LL n, m;
cin >> n >> m;
LL k = sqrt(m);
cout << combinations(k, n) << '\n';
return 0;
}
import java.util.Scanner;
import java.lang.Math;
public class Main {
static final int MOD = 1_000_000_007;
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 inverse(long n) {
return power(n, MOD - 2);
}
static long combinations(long k, long n) {
if (n < 0 || n > k) {
return 0;
}
if (n == 0 || n == k) {
return 1;
}
if (n > k / 2) {
n = k - n;
}
long numerator = 1;
for (long i = 0; i < n; i++) {
numerator = (numerator * (k - i)) % MOD;
}
long denominator = 1;
for (long i = 1; i <= n; i++) {
denominator = (denominator * i) % MOD;
}
return (numerator * inverse(denominator)) % MOD;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
long m = sc.nextLong();
long k = (long) Math.sqrt(m);
System.out.println(combinations(k, n));
}
}
import math
MOD = 1_000_000_007
def combinations(k, n, mod):
if n < 0 or n > k:
return 0
if n == 0 or n == k:
return 1
if n > k // 2:
n = k - n
numerator = 1
for i in range(n):
numerator = (numerator * (k - i)) % mod
denominator = 1
for i in range(1, n + 1):
denominator = (denominator * i) % mod
return (numerator * pow(denominator, mod - 2, mod)) % mod
n, m = map(int, input().split())
k = int(math.sqrt(m))
print(combinations(k, n, MOD))
算法及复杂度
- 算法:数学、组合计数
- 时间复杂度:
。计算组合数需要
的循环,求模逆元需要
的快速幂。由于
,此复杂度足够高效。
- 空间复杂度:
。