hdu 6053 trick gcd 容斥
http://acm.hdu.edu.cn/showproblem.php?pid=6053
题意:给定一个数组,我们定义一个新的数组b满足bi<ai 求满足gcd(b1,b2....bn)>=2的数组b的个数
题解:利用容斥定理。我们先定义一个集合f(x)表示gcd(b1,b2...bn)为x倍数的个数(x为质数),我们在定义一个数mi为数组中的最小值,那么集合{f(2)Uf(3)....f(n)}就是我们想要的答案。f(x)=(a1/x)*(a2/x)*.....(ai/x),直接累加肯定是有重复的,我们得用容斥定理筛一下,如果x是奇数个不同素数因子的乘积最后的结果要加上f(x);如果x为偶数个不同素数因子的乘积,最后的结果要减去f(x),其他情况贡献为0。是不是和莫比乌斯函数的情况正好相反?这里筛值的时候,用0(n)求到的莫比乌斯函数筛时间复杂度还是可以的。光这样还是不够,因为数组的长度为1e5,我们求f(x)的时候也得优化,怎么优化呢。我们把用一个权值数组把a[i]的值离散上去,为啥要用权值的形式存放a数组的值呢,我们把权值数组分成x段,每段的贡献由1开始递增到x(这段比较抽象,具体看下代码)
ac代码:
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; typedef long long ll; const int mod=1e9+7; ll mu[100001]; int prime[100001],vis[100001]; ll a[100001],sum[200001]; // 比较大的数组还是定义在外面比较好 ll qpow(ll a,ll b) { ll f=1; while(b) { if(b%2==1) f=(f*a)%mod; a=(a*a)%mod; b/=2; } return f; } void init() { mu[1]=1; memset(mu,0,sizeof(mu)); memset(prime,0,sizeof(prime)); memset(vis,0,sizeof(vis)); int ret=0; for(int i=2;i<100007;i++) { if(!vis[i]) { prime[ret++]=i; mu[i]=-1LL; } for(int j=0; j<ret && i*prime[j] < 100007;j++) { int temp=i*prime[j]; vis[temp]=1; if(i%prime[j]) mu[temp]=-mu[i]; else { mu[temp]=0; break; } } } } int main() { int t; scanf("%d",&t); init(); int Case=0; while(t--) { ll n; cin>>n; ll mi=100005; memset(sum,0,sizeof(sum)); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); mi=min(a[i],mi); sum[a[i]]++; } for(int i=1;i<=200000;i++) sum[i]+=sum[i-1]; ll zz=0; for(int i=2;i<=mi;i++) { ll ans=1; if(mu[i]==0) continue; for(int j=1;j*i<=100000;j++)// 平铺分段的思想 枚举贡献的思想吧,对于f(x)来说,x/n相同的值比较多,这样就可以把问题的规模变小 这个思维比较常见 { ans=(ans*qpow(j,sum[i*(j+1)-1]-sum[j*i-1])%mod)%mod; } zz=(zz-mu[i]*ans%mod+mod)%mod;// ! 取模的时候 如果有减法 要注意 } printf("Case #%d: ",++Case); cout<<zz<<endl; } return 0; }