容斥——不和睦三元组(再次用到了筛法优化,长点心)
和睦数三元组的个数问题
给出一个整数 。选出a, b, c (其中2<=a<b<c<=n),组成和睦三元组,即:
· 或者满足 , ,
· 或者满足
首先,我们考虑它的逆问题:也就是不和睦三元组的个数。
然后,我们可以发现,在每个不和睦三元组的三个元素中,我们都能找到正好两个元素满足:它与一个元素互素,并且与另一个元素不互素。
所以,我们只需枚举2到n的所有数,将每个数的与其互素的数的个数和与其不互素的数的个数相乘,最后求和并除以2,就是要求的逆问题的答案。
现在我们要考虑这个问题,如何求与2到n这些数互素(不互素)的数的个数。虽然求解与一个数互素数的个数的解法在前面已经提到过了,但在此并不合适,因为现在要求2到n所有数的结果,分别求解显然效率太低。
所以,我们需要一个更快的算法,可以一次算出2到n所有数的结果。
这里注意一下,判断两个数是不是互素,将这两个数字唯一分解,然后看有没有相同的数根就ok了。也正是应为这个,这里可以用容斥和筛法处理这个问题。
在这里,我们可以使用改进的埃拉托色尼筛法。
· 首先,对于2到n的所有数,我们要知道构成它的素数中是否有次数大于1的,为了应用容斥原理,我们还有知道它们由多少种不同的素数构成。
对于这个问题,我们定义数组deg[i]:表示i由多少种不同素数构成,以及good[i]:取值true或false,表示i包含素数的次数小于等于1是否成立。
再利用埃拉托色尼筛法,在遍历到某个素数i时,枚举它在2到n范围内的所有倍数,更新这些倍数的deg[]值,如果有倍数包含了多个i,那么就把这个倍数的good[]值赋为false。
· 然后,利用容斥原理,求出2到n每个数的cnt[i]:在2到n中不与i互素的数的个数。
回想容斥原理的公式,它所求的集合是不会包含重复元素的。也就是如果这个集合包含的某个素数多于一次,它们不应再被考虑。
所以只有当一个数i满足good[i]=true时,它才会被用于容斥原理。枚举i的所有倍数i*j,那么对于i*j,就有N/i个与i*j同样包含i(素数集合)的数。将这些结果进行加减,符号由deg[i](素数集合的大小)决定。如果deg[i]为奇数,那么我们要用加号,否则用减号。
代码:
#include <cstdio> #include <cstring> #include <iostream> #define MAXN 1009 using namespace std; int n; int good[MAXN]; int deg[MAXN], cnt[MAXN]; long long solve() { int ans=0; memset(cnt,0,sizeof(cnt)); memset(deg,0,sizeof(deg)); fill(good,good+MAXN,1); for(int i=2;i<=n;i++) { if(good[i]) { if(deg[i]==0) deg[i]=1; for(int j=1;j*i<=n;j++) // 目的是要筛出不同素数的乘积 { if(j>1 && deg[i]==1 )// 当i为素数的时候,我们才筛去和i具有相同数根的数字 { if(j%i==0) good[i]=0; else deg[i*j]++; } if(deg[i*j]%2) cnt[i*j]+=n/i; else cnt[i*j]-=n/i; } } ans+=cnt[i]; } } int main() { n=100; solve(); return 0; }
最后小结一下,关于筛法的问题。对于多个要求解的目标,如果这些目标都是可以通过前面的一点特定的数推算而来,就适用于筛法。(后来想了想,貌似和树根有关系的题目,都可以考虑一下要不要用筛法优化)。