选数 [Bzoj 3930,CQOI2015]

45 篇文章 0 订阅
33 篇文章 0 订阅

题目地址请点击——


选数


Description

我们知道,从区间 [L,H] L H 为整数)中选取 N 个整数,总共有(HL+1)N 种方案。
小z很好奇这样选出的数的最大公约数的规律,他决定对每种方案选出的 N 个整数都求一次最大公约数,以便进一步研究。
然而他很快发现工作量太大了,于是向你寻求帮助。
你的任务很简单,小z会告诉你一个整数 K,你需要回答他最大公约数刚好为 K 的选取方案有多少个。
由于方案数较大,你只需要输出其除以 1000000007 的余数即可。


Input

输入一行,包含 4 个空格分开的正整数,依次为 NKL H


Output

输出一个整数,为所求方案数。


Sample Input

2 2 2 4


Sample Output

3


HINT

样例解释

所有可能的选择方案:(2,2),(2,3),(2,4),(3,2),(3,3),(3,4),(4,2),(4,3),(4,4)

其中最大公约数等于 2 的只有 3 组: (2,2),(2,4),(4,2)

数据范围

对于 100% 的数据, 1N,K109 1LH109 HL105


Solution

f(d) 表示选取的数最大公约数为 d 的方案数,则

f(d)=d|iμ(id)(hil1i)n=i=1hdμ(i)(hdil1di)n

然后就可以像上一题一样,维护一下 μ 函数的前缀和,分块优化。
可是这里的前缀和不好求,怎么办呢?

定义梅滕斯函数 M(n)=ni=1μ(i) ,给定正整数 n ,计算 M(n)

1=i=1n[i=1]=i=1nd|iμ(d)=i=1nd=1niμ(d)=i=1nM(ni)

因此 M(n)=1ni=2M(ni) ,问题可在 O(n23) 时间复杂度下解决1


Code

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

#define LL long long
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define Min(x,y) ((x)<(y)?(x):(y))

using namespace std;

LL n,k,l,h,ans;
LL minx;
bool no_prime[10000010];
LL prime[10000010];
LL miux[10000010];
LL sum[10000010];
map<int,int>hm;

LL power(LL x,LL y){
    if(y==0)return 1;
    if(y==1)return x%MOD;
    LL tmp=power(x,y/2);
    if(y&1)return tmp*tmp%MOD*(x%MOD)%MOD;
    else return tmp*tmp%MOD;
}

LL solve(LL x){
    LL ttt=0;
    if(x<=minx)return sum[x];
    if(hm.find(x)!=hm.end())return hm[x];
    for(LL i=2,last;i<=x;i=last+1){
        last=x/(x/i);
        ttt+=solve(x/i)*(last-i+1);
    }
    hm[x]=1-ttt;
    return 1-ttt;
}

int main(){

    scanf("%lld%lld%lld%lld",&n,&k,&l,&h);

    if(l>h)swap(l,h);

    minx=Min(h,1e+7);
    miux[1]=1;

    for(LL i=2;i<=minx;i++){
        if(!no_prime[i]){
            prime[++prime[0]]=i;
            miux[i]=-1;
        }
        for(LL j=1;j<=prime[0];j++){
            if(i*prime[j]>minx)break;
            no_prime[i*prime[j]]=true;
            if(i%prime[j]==0){miux[i*prime[j]]=0;break;}
            miux[prime[j]*i]=-miux[i];
        }
    }

    for(LL i=1;i<=minx;i++)sum[i]+=sum[i-1]+miux[i];

    h/=k;l=(l-1)/k;

    for(LL i=1,last;i<=h;i=last+1){
        last=Min(h/(h/i),((l/i==0?INF:l/(l/i))));
        ans=((ans+((solve(last)-solve(i-1))*power(h/i-l/i,n)%MOD))%MOD+MOD)%MOD;
    }

    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值