容斥原理
本问题与欧拉函数类似,如果受到了欧拉函数的启发,就很容易想到用总数n减去1∼n中“不能整除任何一个质数”的数量这个方法,但是本问题并没有保证n能够整除每一个质数p,那么这样的方法就不适用了。而本问题适用另一个更通用的方法,即容斥原理。
考研数学概率论中关于两事件A,B同时发生的概率,有一个更通用的结论:P(AB)=P(A)+P(B)−P(A∩B),衍生到三事件A,B,C,则P(ABC)=P(A)+P(B)+P(C)−P(A∩B)−P(B∩C)−P(A∩C)+P(A∩B∩C)。由此得出,对于n个事件A1∼An,其同时发生的概率P(A1A2…An)可以用∑(−1)JS+1∗P(S)表示。其中S为A1,A2,…An中任意事件的交集,JS表示相交构成S的事件Ai的数量。该求和式暗含了一个信息,就是如果事件S由奇数个Ai相交而成,那么在结果中就需要累加P(S);如果事件S由偶数个Ai相交而成,那么在结果中就需要减去P(S)。在该问题中,事件Ai为“在1∼n中选出一个数,它能被质数pi整除,其概率为P(Ai)=⌊n/pi⌋n,分子就是能被pi整除的数量,它同样适用容斥原理。多个事件Ai相交时的概率则需要把上式中的pi换成若干p的乘积。所有的p构成的集合中,每一个元素被选中的情况,可以用若干二进制位来表示。1代表选中,0代表未选中。这个方法,在之后的状态压缩中也会用到。
C++ 代码
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
int* primes = new int[m];
int ans = 0;
for (int i = 0; i < m; i++) cin >> primes[i];
for (int i = 1; i < 1 << m; i++) { //用m位二进制数表示各质数参不参与除法
size_t divisor = 1, cnt = 0;
for (int j = 0; j < m; j++) {
if ((i >> j) & 1) { //右移取得每一位
cnt++;
divisor *= primes[j]; //除数累乘
if (divisor > n) break; //超过n的话,不大于n的整数中不可能存在整除现象,直接结束
}
}
if (divisor > n) continue; //同理,不用考虑
if (cnt % 2 == 1) ans += n / (int)divisor; //按照容斥原理,增加或减少
else ans -= n / (int)divisor;
}
cout << ans << endl;
delete[] primes;
return 0;
}