题目
从区间[l∼r][l\sim r][l∼r]中选取nnn个整数,总共有(r−l+1)n(r-l+1)^n(r−l+1)n种方案。问最大公约数刚好为kkk的选取方案有多少个。
分析
那么也就是在求[⌈lk⌉∼⌊rk⌋]中选取[\lceil\frac{l}{k}\rceil\sim\lfloor\frac{r}{k}\rfloor]中选取[⌈kl⌉∼⌊kr⌋]中选取n个整数,最大公约数刚好为1的选取方案数个整数,最大公约数刚好为1的选取方案数个整数,最大公约数刚好为1的选取方案数
那么之后怎么做呢,因为r−lr-lr−l比较小,所以说可以选择枚举因数iii,那么缩小的范围就变为[⌈⌈lk⌉i⌉∼⌊⌊rk⌋i⌋][\lceil\frac{\lceil\frac{l}{k}\rceil}{i}\rceil\sim\lfloor\frac{\lfloor\frac{r}{k}\rfloor}{i}\rfloor][⌈i⌈kl⌉⌉∼⌊i⌊kr⌋⌋],那么其中的数就是对于因数为iii时,范围内除以iii的数,设dp[i]dp[i]dp[i]为选取的数公因数是iii的个数,那么dp[i]=(⌊⌊rk⌋i⌋−⌈⌈lk⌉i⌉+1)n−(⌊⌊rk⌋i⌋−⌈⌈lk⌉i⌉+1)dp[i]=(\lfloor\frac{\lfloor\frac{r}{k}\rfloor}{i}\rfloor-\lceil\frac{\lceil\frac{l}{k}\rceil}{i}\rceil+1)^n-(\lfloor\frac{\lfloor\frac{r}{k}\rfloor}{i}\rfloor-\lceil\frac{\lceil\frac{l}{k}\rceil}{i}\rceil+1)dp[i]=(⌊i⌊kr⌋⌋−⌈i⌈kl⌉⌉+1)n−(⌊i⌊kr⌋⌋−⌈i⌈kl⌉⌉+1)
那问题是我们求的是1,那可以干什么呢,我们枚举i的倍数,dp[i]−=∑j=2xdp[ij]dp[i]-=\sum_{j=2}^{x}dp[ij]dp[i]−=∑j=2xdp[ij],这样就可以求出最大公因数是iii的个数,时间复杂度O(2×(r−l)log2(r−l))O(2\times (r-l)\log_2(r-l))O(2×(r−l)log2(r−l))
代码
#include <cstdio>
#define rr register
using namespace std;
const int mod=(int)1e9+7;
int n,k,l,r,dp[100001];
inline signed ksm(long long x,int y){
rr long long ans=1;
while (y){
if (y&1) (ans*=x)%=mod;
(x*=x)%=mod; y>>=1;
}
return ans;
}
signed main(){
scanf("%d%d%d%d",&n,&k,&l,&r);
l=l/k+(l%k>0),r/=k;
if (l>r) return !putchar(48);
for (rr int i=1;i<=r-l;++i){
rr int L=l/i+(l%i>0),R=r/i;
if (L>R) break;
dp[i]=(ksm(R-L+1,n)-R+L-1+mod)%mod;
}
for (rr int i=r-l;i;--i)
for (rr int j=2;i*j<=r-l;++j)
(dp[i]-=dp[i*j]-mod)%=mod;
if (l==1) (++dp[1])%=mod;//如果最后扩散到的范围在1上,那么1要多加
return !printf("%d",dp[1]);
}