[JZOJ5134]三元组/[SPOJ PCOPTRIP]Counting Pairwise Coprime Triples

题目大意

给定三个整数 A,B,C ,一个三元组 (i,j,k) 是合法的当且仅当满足:
 i,j,k 均为整数
 1iA,1jB,1kC
 gcd(i,j)=gcd(i,k)=gcd(j,k)=1
请求出合法的三元组数量对 109+7 取模的结果。

1A,B,C5×104

题目分析

大力推式子:

i=1Aj=1Bk=1C[gcd(i,j)=gcd(j,k)=gcd(k,i)=1]=i=1Aj=1,gcd(i,j)=1Bk=1,gcd(i,k)=1C[gcd(j,k)=1]=i=1Aj=1,gcd(i,j)=1Bk=1,gcd(i,k)=1Cd|j,d|kμ(d)=i=1Ad=1gcd(d,i)=1Aμ(d)j=1,d|j,gcd(i,j)=1Bk=1,d|k,gcd(i,k)=1C1=i=1Ad=1,gcd(d,i)=1Aμ(d)j=1,gcd(i,j)=1Bdk=1,gcd(i,k)=1Cd1

最外层的 i 直接枚举,考虑解决这样一个问题:
d=1,gcd(d,i)=1Aμ(d)j=1,gcd(i,j)=1Bdk=1,gcd(i,k)=1Cd1=d=1,gcd(d,i)=1Aμ(d)j=1Bd[gcd(i,j)=1]k=1Cd[gcd(i,k)=1]

d 分块,那么我们相当于要解决两个问题:
d=1nμ(d)×[gcd(d,i)=1]

以及
d=1n[gcd(d,i)=1]

首先这个肯定可以使用洲阁筛完成。但是如果我们用洲阁筛来算的话,每一次换一个 i 都要重新筛,时间复杂度和常数都挺大的,跑不过去。
考虑设 S(D,k) 表示 1 D 中与 k 互质的数的 μ 函数之和。这里的 k 一定是形如 ki=1pi(piP) 的数,即实质上一个质因子集合。
类似洲阁筛的思路,我们可以得到如下的式子:
S(D,k)=S(D,kpk)+S(Dpk,k)

其中 pk 表示 k 的最小质因子。
类似地,我们设 T(D,k) 来计算第二个问题,可以得到
T(D,k)=T(D,kpk)T(Dpk,kpk)

注意有用的 D 值只有 O(A) ,因此计算 S T 只需要 O(AA) 的时间复杂度。
这样算的好处就是,我的 S T 的计算并不依赖于 i 具体是多少,一次预处理就可以在后面直接利用了。
总的时间复杂度是 O(AA) ,当然你的程序可能需要各种优化技巧才能通过此题。

代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <map>

using namespace std;

const int P=1000000007;
const int N=50000;
const int L=224;
const int M=20;

int pri[N+5],f[N+5],p[N+5],mu[N+5],ms[N+5],nxt[N+5],base[N+5],id[N+5],basans[N+5];
int S[N+5][L*3],T[N+5][L*3];
bool exist[N+5];
int bin[L*3];
int A,B,C,l,cnt1,cnt2,cnt;

void pre()
{
    ms[1]=mu[1]=f[1]=base[1]=1;
    for (int i=2;i<=C;++i)
    {
        if (!f[i]) pri[++pri[0]]=f[i]=base[i]=i,mu[i]=-1;
        for (int j=1,x;j<=pri[0];++j)
        {
            if (1ll*pri[j]*i>C) break;
            f[x=pri[j]*i]=pri[j],mu[x]=mu[i]*(f[x]==f[i]?0:-1),base[x]=base[i]*(f[x]==f[i]?1:f[x]);
            if (!(i%pri[j])) break;
        }
        ms[i]=ms[i-1]+mu[i];
    }
    for (int i=1;i<=C;++i) if (base[i]==i) nxt[i]=i/f[i];
    cnt=0;
    for (int i=1;i<=B;++i) if (!exist[B/i]) exist[B/i]=1,bin[++cnt]=B/i;
    for (int i=1;i<=C;++i) if (!exist[C/i]) exist[C/i]=1,bin[++cnt]=C/i;
    sort(bin+1,bin+1+cnt);
    for (int i=1;i<=cnt;++i) id[bin[i]]=i;
    for (int i=1;i<=cnt;++i) S[1][i]=ms[bin[i]],T[1][i]=bin[i];
    for (int i=2;i<=C;++i)
        if (base[i]==i)
            for (int j=1;j<=cnt;++j)
                S[i][j]=(S[nxt[i]][j]+S[i][id[bin[j]/f[i]]])%P,T[i][j]=(T[nxt[i]][j]-T[nxt[i]][id[bin[j]/f[i]]]+P)%P;
}

int solve()
{
    int ret=0;
    for (int a=1;a<=A;++a)
    {
        if (!basans[base[a]])
            for (int st=1,en,mus,mul,lst=0;st<=B&&st<=C;st=en,lst=mus)
            {
                en=min(B/(B/st),C/(C/st))+1,mus=S[base[a]][id[en-1]],mul=1ll*T[base[a]][id[B/st]]*T[base[a]][id[C/st]]%P;
                (basans[base[a]]+=1ll*(mus-lst+P)*mul%P)%=P;
            }
        (ret+=basans[base[a]])%=P;
    }
    return ret;
}

int main()
{
    freopen("triple.in","r",stdin),freopen("triple.out","w",stdout);
    scanf("%d%d%d",&A,&B,&C);
    if (B<=A&&B<=C) swap(A,B);
    if (C<=A&&C<=B) swap(A,C);
    if (C<=B) swap(B,C);
    pre(),printf("%d\n",solve());
    fclose(stdin),fclose(stdout);
    return 0;
}

你可能感兴趣的:(数论,OI,分块,狄利克雷卷积,洲阁筛)