在线评测链接:P1148
塔子哥是一名年轻的数学家,他一直对数学充满热情。有一天,他在研究数论时发现了一个有趣的问题:给定一个长度为 n n n 的数组 a a a,问有多少个子数组的乘积的因数个数 ≥ k ≥k ≥k。
塔子哥对这个问题非常感兴趣,因为它可以帮助他更好地了解因数的性质,但是现在研究出现了一些问题,他想请你帮忙解决一下这个问题,你能帮塔子哥解决这个问题吗?
第一行为两个整数 n n n 和 k k k ,( 1 ≤ n ≤ 2 × 1 0 5 1\le n \le 2\times 10^5 1≤n≤2×105 , 1 ≤ k ≤ 1 0 9 1\le k \le 10^9 1≤k≤109 )。
第二行为 n n n 个整数,第 i i i 个数为 a i a_i ai ,( 1 ≤ a i ≤ 2 × 1 0 5 1\le a_i \le 2\times 10^5 1≤ai≤2×105 )。
输出为一个整数,表示有多少个子数组的乘积的因数个数 ≥ k ≥k ≥k。
输入
5 3
1 3 7 5 4
输出
10
输入
8 5
1 3 4 6 7 11 10 3
输出
26
python选手使用pypy3提交获得更快的速度
ps:目前各大笔试平台只有牛客提供pypy3编译器,能够跟C++去比比赛。其他平台,遇到py被卡,基本只能自认倒霉。当然这种情况也只会出现在压轴题。
建议对照代码理解思路
固定某一端点时,子数组越长,区间乘积的因数个数越多。所以可以使用双指针。
最大的问题是双指针移动的时候如何维护约数个数?
如果直接大数模拟区间乘法,然后每次模拟的求约数个数会显然会超时。我们需要考虑因数个数本身的性质。
我们先学习一个数论中的知识点:约数个数定理
根据网址中提到的公式,我们可以维护一个map来存当前区间乘法的唯一分解定理形式 , 键是素数,值是素数对应的指数,举个例子:
60 = 2 2 ∗ 3 ∗ 5 60 = 2^2 * 3 * 5 60=22∗3∗5 , 那么 m a p = { 2 : 2 , 3 : 1 , 5 : 1 } map=\{2:2,3:1,5:1\} map={2:2,3:1,5:1}
考虑维护:对于右端点新增的数,我们将其质因分解然后插入到map中。同理,对于左端点删除的数,我们将其质因分解,从map中删除贡献。那么约数个数也同时维护,举个例子:
现在需要新增一个数为 20 = 2 2 ∗ 5 20=2^2*5 20=22∗5 , 那么即: m a p [ 2 ] + = 2 , m a p [ 5 ] + = 1 map[2]+=2 , map[5]+=1 map[2]+=2,map[5]+=1
上述做法的缺陷是 质因分解的复杂度为 O ( a i ) = O ( n ) O(\sqrt{a_i})=O(\sqrt{n}) O(ai)=O(n) 。 而总体 O ( n n ) O(n\sqrt{n}) O(nn) 的复杂度比较危险。需要更快的解决。
一种显然的做法是开 m a x ( a i ) = 2 e 5 max(a_i) = 2e5 max(ai)=2e5个 l i s t list list , 然后类似埃式筛的做法获得每个值的唯一分解定理形式。这样复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) . 然后双指针转移的时候复杂度就从 O ( n ) O(\sqrt{n}) O(n) 降到 T ( a i ) = π ( a i ) ∈ O ( l o g a i ) T(a_i)=\pi(a_i) \in O(log\ a_i) T(ai)=π(ai)∈O(log ai) 了。 总复杂度变为 O ( n l o g n + n ∗ π ( a i ) ) = O ( n l o g n ) O(nlogn+n*\pi(a_i)) = O(nlogn) O(nlogn+n∗π(ai))=O(nlogn)
解释:其中函数 π ( a i ) \pi(a_i) π(ai) 代表 a i a_i ai唯一分解定理表示法中质数的个数.例如: 60 = 2 ∗ 2 ∗ 3 ∗ 5 , π ( 60 ) = 4 60=2*2*3*5,\pi(60)=4 60=2∗2∗3∗5,π(60)=4 , 可以发现他显然是小于 l o g a i log a_i logai的。
204. 计数质数
练习埃式筛
推荐一些数论题 , 难度依次递增
P1202 2023.04.09-招行-第一题-完美击杀巨龙
数论-质因分解
P1223 2023.04.15-携程春招-第二题-最小公倍数
数论-gcd
P1221-2023.04.23-合肥38所-春招-第三题-整除
数论-同余理论
P1244-2023.04.20-蚂蚁暑期实习-第三题-元素乘积的因子数量
贪心 + 数论
P1119 阿里巴巴-2023.03.26-第三题-数组之和最小值
贪心 + 数论优化
P1126 2023.03.26-腾讯实习-第五题-序列最大公约数
数论+动态规划
P1096 米哈游-2023.03.19-第三题-倍数集合
数论 + 动态规划
#include
using namespace std;
using ll = long long;
using pii = pair;
void solve() {
int n, k;
cin >> n >> k;
vector a(n);
int hi = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
hi = max(hi, a[i]);
}
// 预处理每个值的唯一分解定理形式
vector> d(hi + 1);
for (int i = 2; i <= hi; i++) {
if (!d[i].empty()) continue;
for (int j = i; j <= hi; j += i) {
d[j].push_back(i);
}
}
// 双指针
ll ans = 1LL * n * (n + 1) / 2;
ll cur = 1;
int left = 0;
vector fac(hi + 1);
for (int i = 0; i < n; i++) {
int val = a[i];
for (int f : d[a[i]]) {
while (val % f == 0) {
val /= f;
cur /= (fac[f] + 1);
fac[f]++;
cur *= (fac[f] + 1);
}
}
while (cur >= k && left <= i) {
int val = a[left];
for (int f : d[a[left]]) {
while (val % f == 0) {
val /= f;
cur /= (fac[f] + 1);
fac[f]--;
cur *= (fac[f] + 1);
}
}
left++;
}
ans -= i - left + 1;
}
cout << ans << endl;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
//cin >> t;
while (t--) {
solve();
}
}
from collections import defaultdict
n , k = list(map(int , input().split()))
a = list(map(int , input().split()))
maxa = 200005
f = [defaultdict(int) for i in range(maxa)]
p = [True] * maxa
# 类似埃氏筛的去转移,求解每个数其唯一分解定理表示法(质数,指数大小)。用dict存储
for i in range (2 , maxa):
if not p[i]:
continue
f[i][i] = 1
for j in range (i + i, maxa , i):
if i in f[j // i]:
f[j][i] = f[j // i][i] + 1
else:
f[j][i] = 1
p[j] = False
# 双指针 - now_f 代表当前区间累积的唯一分解表示法 , now_val 代表当前区间累积的约数个数
now_f = defaultdict(int)
now_val = 1
ans = 0
i = 0
j = 0
while j < n:
# 根据<约数个数定理>,进行转移更新
for x in f[a[j]]:
now_val //= now_f[x] + 1
now_f[x] += f[a[j]][x]
now_val *= now_f[x] + 1
while now_val >= k and i <= j:
# 根据<约数个数定理>,进行转移更新
for x in f[a[i]]:
now_val //= now_f[x] + 1
now_f[x] -= f[a[i]][x]
now_val *= now_f[x] + 1
i += 1
ans += i
j += 1
print(ans)
gpt 生成 有问题 待补
import java.util.*;
public class Main {
static long calculate(List<Integer> factors, int n) {
Map<Integer, Integer> cnt = new HashMap<>();
for (int f : factors) {
cnt.put(f, cnt.getOrDefault(f, 0) + 1);
}
long res = 1;
for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {
int p = entry.getKey(), k = entry.getValue();
res *= (k + 1);
}
return n * (n + 1) / 2 - res;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
while (t-- > 0) {
int n = sc.nextInt(), k = sc.nextInt();
int[] a = new int[n];
int hi = 0;
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
hi = Math.max(hi, a[i]);
}
List<Integer>[] d = new ArrayList[hi + 1];
for (int i = 2; i <= hi; i++) {
if (d[i] != null) continue;
d[i] = new ArrayList<>();
for (int j = i; j <= hi; j += i) {
d[j].add(i);
}
}
long ans = n * (n + 1) / 2;
long cur = 1;
int left = 0;
Map<Integer, Integer> fac = new HashMap<>();
for (int i = 0; i < n; i++) {
int val = a[i];
for (int f : d[a[i]]) {
while (val % f == 0) {
val /= f;
int cnt = fac.getOrDefault(f, 0);
cur /= (cnt + 1);
fac.put(f, cnt + 1);
cur *= (cnt + 2);
}
}
while (cur >= k && left <= i) {
int v = a[left];
for (int f : d[a[left]]) {
while (v % f == 0) {
v /= f;
int cnt = fac.get(f);
cur /= (cnt + 1);
fac.put(f, cnt - 1);
cur *= cnt;
}
}
left++;
}
ans -= calculate(d[a[i]], i - left + 1);
}
System.out.println(ans);
}
}
}