[SDOI2018]旧试题

[SDOI2018]旧试题 

Bzoj5332: [Sdoi2018]旧试题

反演+三元环计数+卡常

反演部分:

第一步还是“约数个数和”那个结论的扩展:(考虑质数的次数的分配即可证明)

 

 最后一步就是枚举u,v,w考虑满足能凑出u,v,w的x,y,z有哪些

(式子u,v,w,lcm那里有打错了)

 

还是一脸不可做

三个点形成的三元组(u,v,w)贡献一定。

三元环计数!

 

首先埃拉托尼斯筛把后面三个括号,每个lcm贡献搞出来。之后可以O(1)计算

两个点如果miu(u),miu(v)都是非零的,且lcm<=max(a,b,c)连接lcm(u,v)的边权的边。

统计每个三元环的贡献即可

 

O(m)建边:

枚举lcm为i

miu(u),miu(v)都是非零的,所以miu(lcm(u,v))也是非零的

枚举i的约数a(就是2^k啦,k是质因子个数),再枚举a的约数g,b=(long long)i*g/a

连接(a,b)即可。(容易证明,这个b一定满足gcd(a,b)=g,lcm(a,b)=i)

一共大概76万条边

 

三元环计数是O(msqrt(m))的

卡常:

1.vector存边。利于缓存

2.枚举约数a,g用状压别dfs

3.这题答案不爆long long所以最后%mod 即可

 

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define fi first
#define se second
#define mk(a,b) make_pair(a,b)
#define numb (ch^'0')
using namespace std;
typedef long long ll;
template<class T>il void rd(T &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
template<class T>il void ot(T x){x/10?ot(x/10):putchar(x%10+'0');}
template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) printf("%lld ",a[i]);putchar('\n');}

namespace Miracle{
const int N=1e5+5;
const int mod=1e9+7;
int A,B,C;
int du[N];
vector<int>yin[N],to[N];
vector<int>val[N];
int bian[N*20][3];
int cnt;
ll con[N][3];//A/x B/y C/z
int miu[N],pri[N],tot;
int vis[N];
int wei[N];
ll ans;
void sieve(int n){
    miu[1]=1;
    for(reg i=2;i<=n;++i){
        if(!vis[i]){
            pri[++tot]=i;
            miu[i]=-1;
        }
        for(reg j=1;j<=tot;++j){
            if(pri[j]*i>n) break;
            vis[pri[j]*i]=1;
            if(i%pri[j]==0){
                miu[i*pri[j]]=0;
                break;
            }
            miu[i*pri[j]]=-miu[i];
        }
    }
    for(reg j=1;j<=tot;++j){
        for(reg i=pri[j];i<=n;i+=pri[j]){
            yin[i].push_back(pri[j]);
        }
    }
}
ll huan(int a,int b,int c,int ab,int bc,int ac){
    ll ret=0;
    ret=
    (ll)miu[a]*miu[b]*miu[c]*(
        (ll)con[ab][0]*con[bc][1]*con[ac][2]+
        (ll)con[ac][0]*con[bc][1]*con[ab][2]+
        (ll)con[ab][0]*con[ac][1]*con[bc][2]+
        (ll)con[bc][0]*con[ac][1]*con[ab][2]+
        (ll)con[ac][0]*con[ab][1]*con[bc][2]+
        (ll)con[bc][0]*con[ab][1]*con[ac][2]
    );
    return ret;
}
void pre(int n){
    for(reg i=1;i<=n;++i){
        for(reg j=i;j<=n;j+=i){
            con[i][0]+=A/j;
            con[i][1]+=B/j;
            con[i][2]+=C/j;
        }
    }
    for(reg i=1;i<=n;++i){
        ans+=(ll)miu[i]*miu[i]*miu[i]*con[i][0]*con[i][1]*con[i][2];
    //    cout<<" after "<<i<<" : "<<ans<<endl;
    }    
    for(reg i=2;i<=n;++i){
        if(miu[i]!=0){
            for(reg j=0;j<(int)(1<<yin[i].size());++j){
                int a=1;
                for(reg l=0;l<(int)yin[i].size();++l) if(j&(1<<l)) a*=yin[i][l];
            //    cout<<" j "<<j<<" "<<a<<endl;
                for(reg k=0;k<(1<<yin[a].size());++k){
                    int gcd=1;
                    for(reg l=0;l<(int)yin[a].size();++l) if(k&(1<<l)) gcd*=yin[a][l];
                    int b=(ll)i*gcd/a;
                //    cout<<" k "<<k<<" "<<gcd<<" "<<b<<endl;
                    if(a<b){
                    ///    cout<<" addedge "<<a<<" "<<b<<endl;
                        bian[++cnt][0]=a;
                        bian[cnt][1]=b;
                        bian[cnt][2]=i;
                        ++du[a];++du[b];
                        
                        ans+=
                        (ll)miu[a]*miu[a]*miu[b]*con[a][0]*con[i][1]*con[i][2]+
                        (ll)miu[a]*miu[a]*miu[b]*con[i][0]*con[a][1]*con[i][2]+
                        (ll)miu[a]*miu[a]*miu[b]*con[i][0]*con[i][1]*con[a][2]+
                        (ll)miu[a]*miu[b]*miu[b]*con[b][0]*con[i][1]*con[i][2]+
                        (ll)miu[a]*miu[b]*miu[b]*con[i][0]*con[b][1]*con[i][2]+
                        (ll)miu[a]*miu[b]*miu[b]*con[i][0]*con[i][1]*con[b][2];
                    }
                }
            }
        }
    }
}
void clear(int n){
    ans=0;
    memset(du,0,sizeof du);
    memset(con,0,sizeof con);
    memset(vis,0,sizeof vis);
    memset(wei,0,sizeof wei);
    for(reg i=1;i<=n;++i) to[i].clear(),val[i].clear();
    cnt=0;
}
int main(){
    int t;
    rd(t);
    sieve(N-3);
    memset(vis,0,sizeof vis);
    while(t--){
        rd(A);rd(B);rd(C);
        int n=max(A,max(B,C));
        pre(n);
//        cout<<" ans1 "<<ans<<endl;
        for(reg i=1;i<=cnt;++i){
            int x=bian[i][0],y=bian[i][1];
            if(du[x]<du[y]) swap(x,y);
            to[x].push_back(y);
            val[x].push_back(bian[i][2]);
        }
        for(reg i=1;i<=n;++i){
            for(reg j=0;j<(int)to[i].size();++j){
                int y=to[i][j];
                vis[y]=i;
                wei[y]=val[i][j];
            }
            for(reg j=0;j<(int)to[i].size();++j){
                int y=to[i][j];
                for(reg k=0;k<(int)to[y].size();++k){
                    int z=to[y][k];
                    if(vis[z]==i){
                        ans+=huan(i,y,z,val[i][j],val[y][k],wei[z]);
                    }
                }
            }
        }
        ans%=mod;
        printf("%lld\n",ans);
        clear(n);
    }    
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2019/3/8 18:46:49
*/

 

其实全都是套路。。。。

 

转载于:https://www.cnblogs.com/Miracevin/p/10498197.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值