HDU 6053 TrickGCD(分块)
【题目链接】 http://acm.hdu.edu.cn/showproblem.php?pid=6053
【题目大意】
给出一个数列每个位置可以取到的最大值,
问这个可以构造多少个数列,使得他们的最大公约数大于1
【题解】
我们可以枚举最大公约数k,对于k来说,
他对答案的贡献为∏[ai/k],我们将数列中的数字转化为权值数组
∏_{i=1}^{100000}[i/k],对于求解i/k的部分我们可以进行数值分块,
j*k-1~j*k+k-1的数值除k得到的结果都是相同的,因此可以直接求这个结果的幂次,
这时候只要再加一个权值数组的前缀和,问题就迎刃而解了。
数值分块计算的复杂度为n+n/2+n/3+n/4+n/5+……+n/n=nlogn。
对于计算结果,我们需要进行容斥,奇数次素数乘的系数为1,偶数次素数乘的系数为-1,
对于出现素数幂的合数其系数为0,
我们发现这个容斥恰好是莫比乌斯函数的相反数,因此我们取反即可。
这有个小小的优化,对于系数为0的情况,我们可以直接跳过,不进行计算。
【代码】
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int N=200010; typedef long long LL; const LL mod=1000000007; int T,n,a[N],b[N],cnt[N],cas=1,p[N]; LL ans=0; int tot,miu[N],sum[N],v[N]; void read(int&a){ char ch;while(!((ch=getchar())>='0')&&(ch<='9')); a=ch-'0';while(((ch=getchar())>='0')&&(ch<='9'))a*=10,a+=ch-'0'; } void mobius(int n){ int i,j; for(miu[1]=1,i=2;i<=n;i++){ if(!v[i])p[tot++]=i,miu[i]=-1; for(j=0;j<tot&&i*p[j]<=n;j++){ v[i*p[j]]=1; if(i%p[j])miu[i*p[j]]=-miu[i];else break; } }for(i=1;i<n;i++)sum[i]=sum[i-1]+miu[i]; } LL pow(LL a,LL b,LL p){if(b==0)return 1;LL t=1;for(a%=p;b;b>>=1LL,a=a*a%p)if(b&1LL)t=t*a%p;return t;} int main(){ read(T); mobius(100000); while(T--){ read(n); ans=0; int mn=~0U>>1,mx=0; memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++)read(a[i]),mn=min(a[i],mn),mx=max(a[i],mx),cnt[a[i]]++; for(int i=1;i<=200000;i++)cnt[i]+=cnt[i-1]; for(int i=2;i<=mn;i++){ if(!miu[i])continue; LL tmp=1; for(int j=1;i*j<=100000;j++)tmp=tmp*pow(j,cnt[i*j+i-1]-cnt[i*j-1],mod)%mod; // j<=100000/i -> i*j<=100000 : TLE -> AC ans=(ans-tmp*miu[i]+mod)%mod; }printf("Case #%d: %lld\n",cas++,ans); }return 0; }
愿你出走半生,归来仍是少年