把 x 限制在 b1 的约数上,再按质数位独立计数:每位由 gcd/lcm 条件解出一个指数区间,各位区间长度相乘即答案。
OJ: roj
题目 ID: 3087
难度:提高+/省选-
标签:数论质因数分解gcd最大公约数python
创建: 2026-10-01 16:14
更新: 2026-10-07 13:50
形式化题目
给定四个正整数
条件 2 直接给出
样例 1 是
正解
思路
先想最直接的做法。 既然
这个做法绝对正确,但瓶颈在「枚举约数」这一步:题目有
换成枚举「指数」。 记
于是两条条件拆成逐质数位的约束:对每个质数
因为正整数由它的全部质数位指数唯一确定,而每个
(下文在
graph TD X["x"] -->|"逐质数取 v_p"| E["指数向量 (e_2, e_3, e_5, …)"] E --> C1["条件 1:min(e_p, a_p) = a1_p"] E --> C2["条件 2:max(e_p, b0_p) = b1_p"] C1 --> S["逐位可行集 S_p"] C2 --> S S --> P["答案 = ∏ |S_p|(乘法原理)"]
图展示题目的两条整体条件如何被拆开再合回去:上端把
每一位上到底有几种取法? 由于题面保证
- 若
: 时 逼出 , 时 , 所以只有 一个解(单点约束)。若 : 等价于 (单侧下界)。 - 若
: 时 逼出 , 时 , 所以只有 一个解(单点约束)。若 :等价于 (单侧上界)。
两条约束取交,就得到这一位的取法数:
| 条件 1 的解集 | 条件 2 的解集 | 交集大小 | ||
|---|---|---|---|---|
表的每一行是一种指数格局,第三四列是该行两个条件各自允许的
代码把四格统一成区间求交:单点写成 UNBOUNDED = 10**9
(任何真实指数都远小于它),最后返回 max(0, high - low + 1)。交集为空返回
用样例核对。 样例 1 是
| 质数 |
可行 |
方案数 | ||||
|---|---|---|---|---|---|---|
乘积
实现上的两个细节。 第一,试除只需用到
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 16:14
# update_at: 2026-10-01 16:14
import sys
from math import isqrt
VALUE_LIMIT = 2 * 10**9 # 题面给出的取值上界:a0、a1、b0、b1 都不超过 2·10^9
UNBOUNDED = 10**9 # 指数只被单侧限制时的上界哨兵(任何指数都远小于它)
def primes_upto(limit: int) -> tuple[int, ...]:
"""筛出不超过 limit 的质数:分解 a0 与 b1 时,试除只需用到 √(2·10^9) 以内的质数。"""
sieve = bytearray([1]) * (limit + 1)
sieve[0:2] = bytes(2) # 0 和 1 不是质数
for p in range(2, isqrt(limit) + 1):
if sieve[p]:
sieve[p * p :: p] = bytes(len(range(p * p, limit + 1, p))) # 从 p² 起划掉 p 的倍数
return tuple(i for i, ok in enumerate(sieve) if ok)
PRIMES = primes_upto(isqrt(VALUE_LIMIT)) # 4648 个质数
def exponent_count(a: int, a1: int, b0: int, b1: int) -> int:
"""固定质数 p 后,合法指数 e = v_p(x) 的取值个数;四个参数是 a0、a1、b0、b1 的 v_p。
两个条件各自把 e 限制成「一个定值」或「一段区间」:
1. min(e, a) = a1:若 a1 < a 则 e 只能是 a1,否则 e ⩾ a1;
2. max(e, b0) = b1:若 b0 < b1 则 e 只能是 b1,否则 e ⩽ b1。
两者的交集长度就是这一位上 e 的方案数,交集为空返回 0(整题答案随之变成 0)。
"""
lo1, hi1 = (a1, a1) if a1 < a else (a1, UNBOUNDED) # 条件 1 对 e 的限制
lo2, hi2 = (b1, b1) if b0 < b1 else (0, b1) # 条件 2 对 e 的限制
low, high = max(lo1, lo2), min(hi1, hi2)
return max(0, high - low + 1)
def solve() -> None:
data = iter(sys.stdin.buffer.read().split())
out: list[str] = []
n = int(next(data))
for _ in range(n):
a0, a1, b0, b1 = int(next(data)), int(next(data)), int(next(data)), int(next(data))
total = 1
# 逐质数处理:p 不同时整除 a0 与 b1 时四个指数全是 0,这一位的方案数固定为 1。
# 两数边除边缩小,p² 一旦超过剩下的最大者,剩下的部分就必定是 1 或一个质数。
for p in PRIMES:
if p * p > max(a0, b1):
break
if a0 % p and b1 % p:
continue
count_a = count_a1 = count_b0 = count_b1 = 0
while a0 % p == 0:
a0, count_a = a0 // p, count_a + 1
while a1 % p == 0: # a1 | a0,指数一定不超过上面那个
a1, count_a1 = a1 // p, count_a1 + 1
while b0 % p == 0: # b0 | b1,同理
b0, count_b0 = b0 // p, count_b0 + 1
while b1 % p == 0:
b1, count_b1 = b1 // p, count_b1 + 1
total *= exponent_count(count_a, count_a1, count_b0, count_b1)
# 剩下的四段都是 1 或某个大质数(指数只能取 0/1),且互不相同,逐个收尾。
# q > √(2·10^9) 保证 q² 超出值域,所以 v 被 q 整除就等价于 v == q。
for q in {a0, a1, b0, b1} - {1}:
exponents = [int(v % q == 0) for v in (a0, a1, b0, b1)]
total *= exponent_count(*exponents)
out.append(str(total))
print('\n'.join(out))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 18:11
* update_at: 2026-10-06 18:11
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll SIEVE_LIMIT = 44721; // 只需筛出 sqrt(2·10^9) 以内的质数,用于分解 a0 与 b1
const ll UNBOUNDED = 1000000000; // 指数只被单侧限制时的上界哨兵,任何真实指数都远小于它
ll n;
ll a0, a1, b0, b1;
char not_prime[SIEVE_LIMIT + 5]; // 埃氏筛标记:1 表示合数
ll primes[5000]; // 筛出的质数,sqrt(2·10^9) 以内共有 4648 个
ll prime_cnt; // primes[1..prime_cnt] 有效
// 筛出试除分解所需的质数表。
void build_primes() {
for (ll i = 2; i <= SIEVE_LIMIT; i++) {
if (not_prime[i]) continue;
primes[++prime_cnt] = i;
for (ll j = i * i; j <= SIEVE_LIMIT; j += i) {
not_prime[j] = 1;
}
}
}
// 固定一个质数 p 后,合法指数 e = v_p(x) 的取值个数。
// 参数依次是 v_p(a0)、v_p(a1)、v_p(b0)、v_p(b1) 的指数值。
// 条件 min(e, a) = a1 与 max(e, b0) = b1 各自把 e 限制成「一个定值」或「一段区间」,
// 取交集后数区间内的整数个数;交集为空返回 0,整题答案随之变成 0。
ll exponent_count(ll exp_a, ll exp_a1, ll exp_b0, ll exp_b1) {
ll low1, high1;
if (exp_a1 < exp_a) {
low1 = exp_a1;
high1 = exp_a1; // a1 < a 时条件 1 逼出唯一定值 e = a1
} else {
low1 = exp_a1;
high1 = UNBOUNDED; // a1 = a 时条件 1 等价于下界 e >= a1
}
ll low2, high2;
if (exp_b0 < exp_b1) {
low2 = exp_b1;
high2 = exp_b1; // b0 < b1 时条件 2 逼出唯一定值 e = b1
} else {
low2 = 0;
high2 = exp_b1; // b0 = b1 时条件 2 等价于上界 e <= b1
}
ll low = max(low1, low2);
ll high = min(high1, high2);
if (high < low) return 0;
return high - low + 1;
}
void solve() {
cin >> n;
for (ll query = 1; query <= n; query++) {
cin >> a0 >> a1 >> b0 >> b1;
ll rest_a0 = a0, rest_a1 = a1, rest_b0 = b0, rest_b1 = b1;
ll ans = 1;
// 逐质数处理:只关心整除 a0 或 b1 的质数,其余位上四个指数都是 0,方案数固定为 1。
// a1 | a0、b0 | b1,所以两个数各自边除边缩小,不影响条件对应的指数关系。
for (ll i = 1; i <= prime_cnt; i++) {
ll p = primes[i];
if (p * p > max(rest_a0, rest_b1)) break; // 剩下的部分已不可能含两个以上该质数因子
if (rest_a0 % p != 0 && rest_b1 % p != 0) continue;
ll exp_a0 = 0, exp_a1 = 0, exp_b0 = 0, exp_b1 = 0;
while (rest_a0 % p == 0) {
rest_a0 /= p;
exp_a0++;
}
while (rest_a1 % p == 0) {
rest_a1 /= p;
exp_a1++;
}
while (rest_b0 % p == 0) {
rest_b0 /= p;
exp_b0++;
}
while (rest_b1 % p == 0) {
rest_b1 /= p;
exp_b1++;
}
ans *= exponent_count(exp_a0, exp_a1, exp_b0, exp_b1);
}
// 试除结束后四段剩余都是 1 或一个大质数(指数只能取 0 或 1),逐个当作新质数收尾。
ll remainder[4] = {rest_a0, rest_a1, rest_b0, rest_b1};
for (ll i = 0; i < 4; i++) {
ll q = remainder[i];
if (q == 1) continue;
bool repeated = false; // 同一个大质数只处理一次
for (ll j = 0; j < i; j++) {
if (remainder[j] == q) {
repeated = true;
break;
}
}
if (repeated) continue;
ll exp_a0 = (rest_a0 % q == 0) ? 1 : 0;
ll exp_a1 = (rest_a1 % q == 0) ? 1 : 0;
ll exp_b0 = (rest_b0 % q == 0) ? 1 : 0;
ll exp_b1 = (rest_b1 % q == 0) ? 1 : 0;
ans *= exponent_count(exp_a0, exp_a1, exp_b0, exp_b1);
}
cout << ans << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
build_primes();
solve();
return 0;
}复杂度
设
总结
题目的两条 gcd / lcm 条件在质因数分解下会逐位解耦,每一位的合法指数只有「一个点」或
「一段区间」两种形态,答案就是各位区间长度之积。这样就把「枚举