bzoj3930(莫比乌斯反演)

图by popoqqq   http://blog.csdn.net/popoqqq/article/details/44917831



这道题,反演的部分应该不是特别难,像我这样的初学者都可以搞清楚,但是这道题需要常见的那个分块优化,需要求mo【】的前缀和,但是数据范围是1e9,肯定是不能预处理出来,然后上面就已经给出了直接计算mo前缀和的方法,sqrt,但是直接计算时间复杂度又不行。。。


至于mo前缀和计算的证明的话,还是需要再啃一啃。

#include
#include
#include
#include
#include
#include
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 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的都是可以这么做



你可能感兴趣的:(莫比乌斯反演)