[莫比乌斯反演] HDU6053: [2017 多校-第2场] TrickGCD

题意

给出一个长度为n的数列A,求有多少个不同的长度为n的B数列满足下列限制:
1BiAi
For each pair(L,R)(1LRn),gcd(BL,BL+1,...,BR)2
n,ai100000
有T组数据, T10

题解

第一次打 HDU 多校,全场抱大腿,都靠两位学长……
先转换一下,题目说的第二个条件显然就等价于 gcd(B1,...,Bn)>=2 .
然后可以容斥一下,就求满足 gcd(B1,...,Bn)=1 的方案数即可。
开始推式子:

i1=1a1i2=1a2...in=1an[gcd(i1,...,in)=1]

i1=1a1i2=1a2...in=1and|gcd(..)μ(k)

d=1min(a)d|i1a1d|i2a2...d|inanμ(d)

d=1min(a)μ(d)i=1naid

然后如果按照传统方法枚举除法分块搞, ni=1aid nn 段。这样是 O(n2nT) 的,会 T 掉。
怎么做呢?可以换个思路,对于每次算 ,我们枚举 aid 的值,然后统计每个值有几个,快速幂即可。
具体来说:
d=1min(a)μ(d)t=1max(a)/dtx,(xaid=ti)

x 怎么求? aid=tdtaid(t+1)1,前缀和就好啦。
复杂度的话 n+n/2+n/3+... 这样的东西约为 nlogn 。在加上快速幂,共 O(nlog2nT)
看上去好像有点危险,但是快速幂的 log 很小,常数也小,是可以过的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline char gc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int getint(){
    char ch=gc(); int res=0,ff=1;
    while(!('0'<=ch&&ch<='9')) ch=='-'?ff=-1:0, ch=gc();
    while('0'<=ch&&ch<='9') res=(res<<3)+(res<<1)+ch-'0', ch=gc();
    return res*ff;
}
const int maxn=200005, N=200000, MOD=1000000007;
typedef long long LL;
int _test,n,ans,_min,_max,a[maxn],sum[maxn],p[maxn],mu[maxn];
bool vis[maxn];
void get_mu(){
    memset(vis,1,sizeof(vis));
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(vis[i]) p[++p[0]]=i, mu[i]=-1;
        for(int j=1;j<=p[0]&&(LL)i*p[j]<=N;j++){
            vis[i*p[j]]=false;
            if(i%p[j]==0){ mu[i*p[j]]=0; break; }
            mu[i*p[j]]=-mu[i];
        }
    }
}
int Pow(int a,int b){
    LL res=1;
    for(LL w=a;b;b>>=1,w=w*w%MOD) if(b&1) res=(res*w)%MOD;
    return res;
}
void Solve(){
    ans=0;
    for(int d=1;d<=_min;d++){
        LL res=(mu[d]+MOD)%MOD;
        for(int t=1;t<=_max/d;t++) res=res*Pow(t,sum[(t+1)*d-1]-sum[t*d-1])%MOD;
        ans=(ans+res)%MOD;
    }
}
int main(){
    freopen("hdu6053.in","r",stdin);
    freopen("hdu6053.out","w",stdout);
    get_mu();
    scanf("%d",&_test);
    for(int ii=1;ii<=_test;ii++){
        memset(sum,0,sizeof(sum));
        n=getint(); _min=1e+9; _max=0; LL tot=1;
        for(int i=1;i<=n;i++) sum[a[i]=getint()]++, tot=(tot*a[i])%MOD, _min=min(_min,a[i]), _max=max(_max,a[i]);
        for(int i=1;i<=N;i++) sum[i]+=sum[i-1];
        Solve();
        printf("Case #%d: %d\n",ii,((tot-ans)%MOD+MOD)%MOD);
    }
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值