容斥——不和睦三元组(再次用到了筛法优化,长点心)

和睦数三元组的个数问题

       给出一个整数 。选出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;
}
View Code

最后小结一下,关于筛法的问题。对于多个要求解的目标,如果这些目标都是可以通过前面的一点特定的数推算而来,就适用于筛法。(后来想了想,貌似和树根有关系的题目,都可以考虑一下要不要用筛法优化)。

原文地址:https://www.cnblogs.com/z1141000271/p/7298186.html