《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
【题目描述】 给出n个数字,你可以任意选择一些数字相乘,相乘之后得到新数字x,x的分数等于x不同质因子数量。
请你计算所有选择数字方案中,x分数的总和。答案对1000000007取模。
【输入格式】 输入第一行为一个正整数n。
第二行包含n个正整数ai。(1≤n≤200000,1≤ai≤1000000)。
【输出格式】 输出一个整数表示答案。
【输入样例】
3
6 1 2
【输出样例】
10
样例{6, 1, 2}有3个数字,3个数字有 2 3 = 8 2^3 = 8 23=8种组合,每种组合内数字相乘得{ ∅ \emptyset ∅, 1, 2, 6, 1×2, 1×6, 2×6, 1×2×6} = { ∅ \emptyset ∅, 1, 2, 2×3, 2, 2×3, 2×2×3, 2×2×3},它们的质因子数量是{0, 0, 1, 2, 1, 2, 2, 2},总和是10。 ∅ \emptyset ∅表示空集。
简单的办法是单独计算每个数的质因子,计算它的分数,然后统计n个数的总分数,但是这种简单办法会超时。对一个数a进行质因数分解是试除 2 2 2 ~ n \sqrt{n} n的内的所有素数;对n个数做质因数分解,总复杂度为 n a n\sqrt{a} na,超时。
本题不用单独计算每个数的分数,而是统一计算。从n个数中任选数字相乘,共有 2 n 2^n 2n种组合。一个质数在一个组合中出现一次,答案加1;统计所有的质数在所有组合中出现的总次数,就是答案。一个质数可能在多少个组合中出现?设i是其中k个数的因子,那么它在 2 n − 2 n − k 2^n-2^{n-k} 2n−2n−k个组合中出现。例如样例{6, 1, 2}中,质数2是{6, 2}这2个数的因子,在{2, 6, 1×2, 1×6, 2×6, 1×2×6}这 2 3 − 2 3 − 2 = 6 2^3-2^{3-2} = 6 23−23−2=6种组合中出现;3是{6}的因子,在{6, 1×6, 2×6, 1×2×6}这 2 3 − 2 3 − 1 = 4 2^3-2^{3-1} = 4 23−23−1=4种组合中出现。答案是6+4 = 10。
本题的解题步骤是:用素数筛得到所有的质数;统计每个质数在n个数中出现的次数k,用 2 n − 2 n − k 2^n-2^{n-k} 2n−2n−k计算它在所有组合中的分数。在做素数筛的同时,统计质数出现的次数,总计算量相当于只做了一次素数筛,复杂度 O ( a l o g 2 l o g 2 a ) O(alog_2log_2a) O(alog2log2a), a ≤ 1000000 a≤1000000 a≤1000000。
【重点】 掌握质因数分解。
计算 2 n 2^n 2n不能用pow()库函数,因为n太大,而pow()不能做超大的幂计算;更不能直接用位运算2<
#include
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
const int MOD = 1e9 + 7;
int cnt[N];
bool not_prime[N];
ll fastpow(ll a, ll n){ //快速幂,计算a^n,并取模
ll ans = 1;
while(n){
if(n & 1) ans = ans * a % MOD;
a = a * a % MOD;
n >>= 1;
}
return ans;
}
int main(){
int n; cin >> n;
for(int i = 1; i <= n; i++) {
int a; scanf("%d",&a);
cnt[a]++;
}
ll ans = 0;
for(int i = 2; i < N; i++) //素数筛
if(!not_prime[i]){ //i是质数
ll k = cnt[i]; //统计质数i出现的次数k
for(int j = 2*i; j < N; j += i) //把i的倍数筛掉,留下的是素数
k += cnt[j], not_prime[j] = true; //统计质数i出现的次数k
if(k)
ans = (ans + fastpow(2, n) - fastpow(2, n-k) + MOD) % MOD; //质数i的得分是2^n-2^(n-k)
}
cout<<ans<<endl;
return 0;
}
import java.util.Scanner;
public class Main {
static final int N = 1000010;
static final int MOD = 1000000007;
static int[] cnt = new int[N];
static boolean[] notPrime = new boolean[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for (int i = 0; i < n; i++) {
int a = sc.nextInt();
cnt[a]++;
}
long ans = 0;
for (int i = 2; i < N; i++) { //素数筛
if (!notPrime[i]) { //i是质数
long k = cnt[i]; //统计质数i出现的次数k
for (int j = 2 * i; j < N; j += i) { //把i的倍数筛掉,留下的是素数
k += cnt[j]; //统计质数i出现的次数k
notPrime[j] = true;
}
if (k != 0)
ans = (ans+fastPow(2,n) - fastPow(2,n-k) + MOD) % MOD; //质数i的得分是2^n-2^(n-k)
}
}
System.out.println(ans);
sc.close();
}
static long fastPow(long a, long n) { //快速幂,计算a^n,并取模
long ans = 1;
while (n > 0) {
if ((n & 1) == 1) ans = ans * a % MOD;
a = a * a % MOD;
n >>= 1;
}
return ans;
}
}
N = 1000010
MOD = 1000000007
cnt = [0] * N
not_prime = [False] * N
def fastpow(a, n): #快速幂,计算a^n,并取模
ans = 1
while n:
if n & 1: ans = ans * a % MOD
a = a * a % MOD
n >>= 1
return ans
n = int(input())
a = list(map(int, input().split()))
for i in a: cnt[i] += 1
ans = 0
for i in range(2, N): #素数筛
if not not_prime[i]: #i是质数
k = cnt[i] #统计质数i出现的次数k
for j in range(2 * i, N, i): #把i的倍数筛掉,留下的是素数
k += cnt[j] #统计质数i出现的次数k
not_prime[j] = True
if k:
ans = (ans + fastpow(2, n) - fastpow(2, n - k) + MOD) % MOD #质数i的得分是2^n-2^(n-k)
print(ans)