【备战秋招】每日一题:研发岗-第三题-又一个数论题

在线评测链接: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 1n2×105 1 ≤ k ≤ 1 0 9 1\le k \le 10^9 1k109 )。

第二行为 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 1ai2×105 )。

输出描述

输出为一个整数,表示有多少个子数组的乘积的因数个数 ≥ k ≥k k

样例1

输入

5 3
1 3 7 5 4

输出

10

样例2

输入

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=2235 , 那么 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=225 , 那么即: 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=2235,π(60)=4 , 可以发现他显然是小于 l o g a i log a_i logai的。

类似题目推荐

LeetCode

204. 计数质数

练习埃式筛

CodeFun2000

推荐一些数论题 , 难度依次递增

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-第三题-倍数集合

数论 + 动态规划

代码

C++

#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();
	}
}

python

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)

Java

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);
        }
    }
}

你可能感兴趣的:(备战2023秋招,算法,java,c++,python,javascript)