hdu 6053
题意:给定一个a序列,让求有多少种b序列满足,b序列的对应值小于等于a序列的值,且b序列的任意区间的数的gcd>=2;
思路:任意区间的数的gcd>=2则说明,b序列任意两个数不互素,这就可以枚举约数,假如约数为k,则a序列的每个数ai的贡献为ai/k向下取整个值,全部乘起来就是约数为k的b序列种类,每种约数之和就是ans,但是会有重复的,后面容斥一下就好了。
#include
#include
#include
#include
#include
#define LL long long
using namespace std;
const int maxn=1e6+10;
const LL mod=1e9+7;
LL x[maxn],dis[maxn],dp[maxn];
LL quickpow(LL a,LL k)
{
if(a==1||k==0) return 1;
LL res=1;
while(k)
{
if(k&1)
res=(res*a)%mod;
a=(a*a)%mod;
k>>=1;
}
return res;
}
LL ans[maxn];
int main()
{
int ncase,Z=0;
scanf("%d",&ncase);
while(ncase--)
{
memset(dis,0,sizeof(dis));
memset(dp,0,sizeof(dp));
memset(ans,0,sizeof(ans));
LL n,Min=100010;
scanf("%lld",&n);
for(int i=0; iscanf("%lld",&x[i]);
Min=min(Min,x[i]);
dis[x[i]]++;//数列值域化
/*
后面要求小于等于某个数,是k的倍数的有过少个,如果每次都跑一边n
的话,时间复杂度会很高,但是我们发现1~k-1除k向下取整都是1而
k~2*k-1除k向下取整都是2,所以他们可以看成同一个值,用快速幂求解
*/
}
for(int i=1; i<=200000; i++)//统计前缀和
dis[i]+=dis[i-1];
for(int k=2; k<=Min; k++)//枚举约数
{
LL sum=1;
for(int j=k; j<=100000; j+=k)//看约数为k的方案有多少种
{
sum=(sum*(LL)quickpow((LL)j/k,dis[j+k-1]-dis[j-1]))%mod;
}
ans[k]=sum;
}
for(int i=100000;i>=2;i--)//手动容斥
{
dp[i]=ans[i];
for(int j=i+i;j<=100000;j+=i)
dp[i]=((dp[i]-dp[j])%mod+mod)%mod;
}
/*
dp[i]代表gcd为i的有多少种,不包括重复的,这就是从后往前容斥的好处,不需要
加两个倍数个倍数,因为他们是不会重复的,比如2和3有6这个公倍数,如果减去了
2和3倍数的数量则6的倍数的数量就减去了2次要加上6的倍数的数量,而从后往前找
到2时,找到6,6代表的只是自己的值,并不会包含自己倍数的值,这样就不用加了
*/
LL sum=0;
for(int i=2;i<=100000;i++)//统计结果
sum+=dp[i],sum%=mod;
printf("Case #%d: %lld\n",++Z,sum);
}
}