1. 程式人生 > >6053 TrickGCD(莫比烏斯反演+容斥思想+分塊字首和技巧)

6053 TrickGCD(莫比烏斯反演+容斥思想+分塊字首和技巧)

題目大意:

給你一個數組 A ,問你有多少不大於 A 的陣列 B 使得 B 中所有元素的最大公因數不為1。(陣列 B 不大於陣列 A 就等價於,對於任意 A 陣列中的元素 a [ i ] 和 B 陣列中對應元素 b [ i ] ,均有:a [ i ] >= b [ i ])

思路:

容斥思想:該問題就可以轉化成求有多少陣列 B 滿足:B 中的所有元素的最大公因數為 1。

莫比烏斯反演:
設:
f(x)x
F(x)x
那麼顯然:

F(x)=x|df(d)
那麼根據莫比烏斯反演公式可知:
f
(x)=x|du(dx)f(d)

那麼我們最後要求的是f(1),既將x=1帶入上式可得:ans=i=1nu(i)f(i)

分塊字首和技巧:
顯然可知:

f(x)=i=1naix
但是我們如果樸素的方法求每一個f(x)需要O(n)的時間複雜度,顯然這樣是承受不了的。

那麼我們想辦法再繼續優化一下上式:

f(x)=i=0amax/xinum(i,x)
ps:這裡的sum(i)表示的是: A 陣列中有多少數除 x 向下取整為 i 。

那麼現在我們設:sum[i]表示 A 陣列中,值在區間[0,i]的元素個數。
下面給出num(i,x)的計算公式:

num(i,x)=s
um[x(i+1)1]sum[xi1]

ps:Axi=A[xi,x(i+1)1]

如此可將時間複雜度降為:O(n(logn)2)

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define maxn 100500
#define MOD 1000000007
#define mod(x) ((x)%MOD+MOD)%MOD
long long int a[maxn],mu[maxn],num[maxn];
int cas=1,n,m,vis[maxn],prime[maxn];

void
init_mu() { memset(vis,0,sizeof(vis)); mu[1] = 1; int cnt = 0; for(int i=2; i<maxn; i++) { if(!vis[i]) { prime[cnt++] = i; mu[i] = -1; } for(int j=0; j<cnt&&i*prime[j]<maxn; j++) { vis[i*prime[j]] = 1; if(i%prime[j]) mu[i*prime[j]] = -mu[i]; else { mu[i*prime[j]] = 0; break; } } } } long long int fast_pow(long long int s,long long int x) { long long int ans=1; while(x>0) { if(x&1) { ans*=s; ans=mod(ans); } x>>=1; s*=s; s=mod(s); } return mod(ans); } long long int F(int x) { long long int ans=1; for(int i=0;i<=maxn/x;i++) { ans*=fast_pow((long long int)i,num[min(x*(i+1),maxn)-1]-num[min(x*i,maxn)-1]); ans=mod(ans); } return mod(ans); } int main() { int T; scanf("%d",&T); init_mu(); while(T--) { m=maxn<<1; scanf("%d",&n); memset(num,0,sizeof(num)); for(int i=0;i<n;i++) { scanf("%lld",&a[i]); num[a[i]]++; if(a[i]<m)m=a[i]; } for(int i=1;i<maxn;i++) { num[i]+=num[i-1]; } long long int ans=0; for(int i=2;i<=m;i++) { if(mu[i]==0)continue; if(mu[i]==1)ans-=F(i); else ans+=F(i); ans=mod(ans); } ans=mod(ans); printf("Case #%d: %lld\n",cas++,mod(ans)); } }