图by popoqqq http://blog.csdn.net/popoqqq/article/details/44917831
这道题,反演的部分应该不是特别难,像我这样的初学者都可以搞清楚,但是这道题需要常见的那个分块优化,需要求mo【】的前缀和,但是数据范围是1e9,肯定是不能预处理出来,然后上面就已经给出了直接计算mo前缀和的方法,sqrt,但是直接计算时间复杂度又不行。。。
至于mo前缀和计算的证明的话,还是需要再啃一啃。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e7;
const int inf=0x3f3f3f3f;
const int mod=1000000007;
ll n,k,l,h;
ll p[N],mo[N+10],s[N+10];
bool b[N+10];
map<ll,ll> mp;
void init()
{
mo[1]=s[1]=1;
for (int i=2;i<=N;i++)
{
if (!b[i])
{
mo[i]=-1;
p[++p[0]]=i;
}
for (int j=1;j<=p[0]&&p[j]*i<=N;j++)
{
b[i*p[j]]=true;
if (i%p[j]==0)
{
mo[i*p[j]]=0;
break;
}
else mo[i*p[j]]=-mo[i];
}
s[i]=s[i-1]+mo[i];
}
}
ll power(ll a,ll b)
{
ll ans=1;
while (b)
{
if (b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll sum(ll n)
{
if (n<=N) return s[n];
if (mp.find(n)!=mp.end()) return mp[n];
ll ans=1;
for (ll i=1,r;i<=n;i=r+1)
{
r=n/(n/i);
if (n/i-1)
ans-=(sum(r)-sum(i-1))*(n/i-1);
}
return mp[n]=ans;
}
ll work(ll l,ll h)
{
ll ans=0;
for (int i=1,r;i<=h;i=r+1)
{
r=min(l/i ? l/(l/i):inf,h/(h/i));
ans+=(sum(r)-sum(i-1))*power(h/i-l/i,n);
ans%=mod;
}
return (ans%mod+mod)%mod;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&k,&l,&h);
init();
printf("%lld",work((l-1)/k,h/k));
return 0;
}
总结:
1:这道题引出一个非常重要的思想,预处理一部分,直接计算一部分,其中很多时候,一个大的数的函数值,是由比他小的数推出来的,并且后面不影响前面的,那么我们其实就可以进行记忆化搜索,搜到小的范围时直接返回预处理的答案,大的范围就直接计算,计算过的值用一个map存起来。
这样的思想在codevs角谷猜想中也用到了,常常配合着记忆化搜索。
2:这道题,求在l~r的区间内选n个数,gcd==d 的方案数,实际上,这一类的gcd==d,我们都可以尝试转化成gcd==1的问题,其实对于这道题就是,l/d~r/d中gcd==1的方案数,转化成这样后进行下底函数分块思路就清楚多了。很多求gcd==d的都是可以这么做