hdu 6053 TrickGCD 篩法+莫比烏斯函式+分塊處理
阿新 • • 發佈:2019-02-11
題意:
給你n個數字,每個位置的數字可以小於等於a[i],求所有gcd(l,r)都滿足大於等於2的情況數;
思路:
首先,比較好想到的就是列舉gcd,那麼每個ai,都有ai/gcd 的選擇,然後n個數累乘.但是我們發現,比如 6 6 的時候 2 3 都計算了6 ,6也算了6.有很多重複的情況沒法處理,所以想到了容斥,可是當時真的不知道怎麼去容斥,感覺太多了很複雜.
首先分塊處理, 當我們列舉gcd為d的時候, 那麼從(kd,(k+1)d-1) 對答案的貢獻都是為k,那麼我們就可以把這些塊一個個的分開處理, 開一個sum,記錄a陣列中,數的範圍屬於(k*d,(k+1)d-1)的數有多少個,最後快速冪一下就可以快速計算出當gcd為d的時候的貢獻,以此類推.
那麼我們下面來看怎麼去重
1.篩法.
去重的時候我們從後往前考慮,因為每一個數都會多算了一次他的倍數所以我們要將他減去,如果我們從前往後考慮,那麼有些答案並不是最終的結果,所以我們從後往前.因為最大的gcd 後面沒有倍數,不會重複,這樣依次往下減,只保留自己的就好了。
#include<bits/stdc++.h> #define Ri(a) scanf("%d", &a) #define Rl(a) scanf("%lld", &a) #define Rf(a) scanf("%lf", &a) #define Rs(a) scanf("%s", a) #define Pi(a) printf("%d\n", (a)) #define Pf(a) printf("%lf\n", (a)) #define Pl(a) printf("%lld\n", (a)) #define Ps(a) printf("%s\n", (a)) #define W(a) while(a--) #define CLR(a, b) memset(a, (b), sizeof(a)) #define MOD 1000000007 #define inf 0x3f3f3f3f #define exp 0.00000001 #define pii pair<int, int> #define mp make_pair #define pb push_back using namespace std; typedef long long ll; const int maxn=1e5+10; const int N=1e5+5; int a[maxn]; ll dp[maxn]; ll sum[maxn]; int t,n; ll qmod(ll a,ll b) { ll res=1; while(b) { if(b&1) res=res*a%MOD; b>>=1; a=a*a%MOD; } return res; } int main() { scanf("%d",&t); int ca=1; while(t--) { int mi=inf; memset(dp,0,sizeof(dp)); memset(sum,0,sizeof(sum)); scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&a[i]); mi=min(mi,a[i]); sum[a[i]]++; } ll a,b; for(int i=1;i<=N;i++) sum[i]+=sum[i-1]; for(int i=2;i<=mi;i++) { dp[i]=1; for(int j=i;j<=N;j+=i) { if(j+i-1>N) b=sum[N]-sum[j-1]; else b=sum[i+j-1]-sum[j-1]; if(b==0) continue; a=j/i; dp[i]=(dp[i]*qmod(a,b))%MOD; } } ll ans=0; for(int i=N;i>=2;i--) { for(int j=2*i;j<=N;j+=i) dp[i]=(dp[i]-dp[j]+MOD)%MOD; ans = (ans + dp[i]) % MOD; } printf("Case #%d: %lld\n",ca++,ans); } return 0; }
2.巧妙利用莫比烏斯函式
其實我們知道,莫比烏斯就是容斥. 公式如下
我們只考慮質因子,設當前gcd=d,計算貢獻時 d可以拆分為k個質數的乘積,由於這裡是對貢獻進行累加操作,所以我們可以知道,當k為偶數我們需要減,當k為奇數我們需要加,而根據莫比烏斯函式可以看出,當k為偶數為+,奇數為-,正好是一個相反的,所以這裡利用相反的莫比烏斯函式,就可以直接去重了,
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; const int maxn=1e5+10; const int N =1e5; int prime[maxn]; int mu[maxn]; int vis[maxn]; ll sum[maxn]; int t,n; //求莫比烏斯函式 // mu[i] == 1 表示質因子不重複且個數為偶 // mu[i] ==-1 表示質因子不重複且個數為奇 // mu[i] == 0 表示存在重複的質因子 void mobius() { int cnt=0; memset(vis,0,sizeof(vis)); mu[1]=1;//n=1為1 for(int i=2;i<=N;i++) { if(!vis[i]) prime[++cnt]=i,mu[i]=-1; for(int j=1;prime[j]*i<=N;j++) { vis[i*prime[j]]=1; if(i%prime[j]==0) { mu[i*prime[j]]=0; break; } mu[i*prime[j]]=-mu[i]; } } return ; } ll qmod(ll a,ll b) { ll res=1; while(b) { if(b&1) res=res*a%mod; b>>=1; a=a*a%mod; } return res; } int main(){ scanf("%d",&t); int ca=1; memset(mu,0,sizeof(mu)); memset(prime,0,sizeof(prime)); mobius(); while(t--) { memset(sum,0,sizeof(sum)); scanf("%d",&n); int x; int mi=maxn; for(int i=0;i<n;i++) { scanf("%d",&x); sum[x]++; mi=min(mi,x); } for(int i=1;i<=N;i++) sum[i]+=sum[i-1]; ll ans=0; ll res=0; for(int i=2;i<=mi;i++) { if(mu[i]==0)//存在重複的質因子直接跳過 continue; ll a,b; res=1; for(int j=i;j<=N;j+=i) { if(j+i-1>N) b=sum[N]-sum[j-1]; else b=sum[i+j-1]-sum[j-1]; a=j/i; if(b==0) continue; res=(res*qmod(a,b))%mod; } if(mu[i]==-1) ans=(ans+res)%mod; else ans=(ans-res+mod)%mod; } printf("Case #%d: %lld\n",ca++,ans); } return 0; }