[bzoj3930] [CQOI2015]选数

题目

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

题解

按照求区间gcd的套路,我们对所有数除以k.对应的将l和r也除以k
这样目标就是在$(l,r]$内找n个gcd恰好为1的数

我们设 $tot[i]$ 表示gcd恰好为i的方案数,$tot2[i]$ 表示gcd为i或i的倍数方案数。

仿照之前的套路,设$x=l/i,y=r/i$ 则在$(x.y]$中任意取n个数都是满足要求的

则$tot2[i]=(y-x)^n$

则$tot[i]=tot2[i]-(y-x)-tot[i*2]-tot[i*3]...$

因为gcd最大只能到$r-l$(再大r就等于l了,这个区间就没东西了)

所以i只用枚举到$r-l$

减去$y-x$是因为如果选n个同样的数a,那么他们的gcd就是a,而a是1e9级别的,在上式中无法排除,所以我们在将上式减去。

但如果k在$(l,r]$内,则全部选k也是合法的答案,要加回一

最后是时间复杂度,设$n=r-l$

上述过程复杂度为 $n/1+n/2+n/3+n/4+..+n/n$

这是一个调和级数,复杂度是$O(nlogn)$

证明:

上式可以放大成 $n/1+n/2+n/2+n/4+n/4+n/4+n/4+..+n/n$

等于$n/1+n/2*2+n/4*4+n/8*8+n/4+...$

也就是 $nlogn$

上面用的都是前开区间,所以要将题目输入的l减一

代码

#include <iostream>
#include <cstdio>
using namespace std;
#define N 100010
#define int long long 
#define mod 1000000007
int tot[N];//gcd刚好是i的情况数(在除了k的情况下) 
int qpow(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1) ans*=a,ans%=mod;
		a*=a;
		a%=mod;
		b>>=1;
	}
	return ans;
}
signed main()
{
	int n,k,l,r;
	cin>>n>>k>>l>>r;
	bool tag=(k>=l&&k<=r);
	//如果gcd在区间内,那么都选gcd这一位也是合法的 
	l--;
	l/=k,r/=k;
	int len=r-l;
	for(int i=len;i;i--)
	{
		int x=l/i,y=r/i;
		if(x>=y) continue;
		tot[i]=qpow(y-x,n)-(y-x);//减去都选同一位的情况 
		for(int j=2;i*j<=len;j++) tot[i]+=mod-tot[i*j],tot[i]%=mod;
		//减去选择的方案gcd为i的倍数的情况 ,使gcd恰好为i 
	}
	cout<<tot[1]+tag;
}

  

posted @ 2020-08-22 11:55  linzhuohang  阅读(188)  评论(0编辑  收藏  举报