HDU 6053 TrickGCD 莫比烏斯反演||篩法
題意:給定一個序列Ai,構造一個序列Bi,滿足Bi<=Ai並且gcd(Bi)>=2. 問有多少種Bi序列
正解:比賽的時候有點容斥的思路,但是感覺容斥太麻煩了,就換了題去搞,完全忘了莫比烏斯反演。。
顯然我們要列舉gcd,然後對於每個gcd求所有(Ai/gcd)的乘積,最後再加起來,但是這個過程會有重複的,
需要容斥,用莫比烏斯函式即可,好像也有用類似於篩法一樣的容斥也能過。
搜的網上題解大多數用莫比烏斯函式的都是在函式前面加一個負號,我不是太懂為什麼,但是自己舉了幾個小例子也是滿足這個負號的,後來看到一篇部落格是直接套用的書上給的莫比烏斯反演公式:
定義f[n]表示n是最大公約數情況下的計數,F[n]為n是公約數情況下的計數。
這樣∑f[i](2<=i<=min(a[i]))就是要求的結果。
這樣我寫就明白多了嘛。。但還是希望能有大佬給解釋一下為什麼直接-μ(n)*F[n]也是可以的。
本題的難點還有一個就是如何快速求得(Ai/gcd)的乘積,我們可以維護一個字首和陣列b[],b[i]表示有多少Ai<=i,
然後我們類似篩法一樣的列舉,假設當前列舉的gcd為d,那麼j * d - 1 到 (j + 1) * d - 1 除以d的商都是j,結合b[]陣列
我們就可以快速求出除以d商為j的Ai有幾個了,這樣複雜度和篩法差不多,nlogn數量級。
我看網上很多dalao用純篩法的思路進行計數和容斥,也是很強的思路。
最後就是這是第一次寫求莫比烏斯函式的程式碼,根據書上的定義寫了很長,結果看網上大佬3行就解決了。。
程式碼:
#include<bits/stdc++.h> #define ll long long #define pb push_back #define fi first #define se second #define pi acos(-1) #define inf 0x3f3f3f3f #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define rep(i,x,n) for(int i=x;i<n;i++) #define per(i,n,x) for(int i=n;i>=x;i--) using namespace std; const int mod = 1e9 + 7; typedef pair<int,int>P; const int MAXN=100010; int gcd(int a,int b){return b?gcd(b,a%b):a;} int f[MAXN];//f為moebius函式值 ll F[MAXN]; /* void mobius(ll mn) //大佬程式碼,mu[]即為moebius值 { mu[1]=1; for(ll i=1;i<=mn;i++){ for(ll j=i+i;j<=mn;j+=i){ mu[j]-=mu[i]; } } } */ void moebius() { f[1] = 1; for(int i = 2; i < MAXN; i++) { if(f[i] == 0) { for(int j = i; j < MAXN; j += i) f[j]++; } } for(int i = 2; i < MAXN; i++) if(f[i] & 1) f[i] = -1; else f[i] = 1; for(int i = 2; i * i < MAXN; i++) { for(int j = i * i; j < MAXN; j += i * i) f[j] = 0; } } int a[MAXN]; int b[MAXN * 2];//字首和陣列 ll qmul(int a, int b) { ll ans = 1, t = a; while(b) { if(b & 1) ans *= t; t *= t; b >>= 1; ans %= mod; t %= mod; } return ans; } int main() { int T; moebius(); //for(int i = 1; i <= 20; i++) //cout << f[i] <<" "; cin >> T; for(int kase = 1; kase <= T; kase++) { int n; scanf("%d", &n); memset(b, 0, sizeof(b)); for(int i = 0; i < n; i++) scanf("%d", a + i), b[a[i]]++; for(int i = 0; i < 2 * MAXN; i++) b[i] += b[i - 1]; int up = *min_element(a, a + n); ll ans = 0; for(int i = 2; i <= up; i++)//列舉gcd { ll num = 1; for(int j = 1; j * i <= MAXN; j++) { num *= qmul(j, b[(j + 1) * i - 1] - b[j * i - 1]); num %= mod; } F[i] = num; //ans -= f[i] * num; //ans = (ans % mod + mod) % mod; } for(int i = 2; i <= up; i++) { ll num = 1; for(int j = 1; j * i <= MAXN; j++) ans += f[j] * F[i * j], ans %= mod; //ans -= f[i] * num; //ans = (ans % mod + mod) % mod; //cout << ans << endl; } printf("Case #%d: %lld\n", kase, ans); } return 0; }
定理:和是定義在非負整數集合上的兩個函式,並且滿足條件,那麼我們得到結論
另一種描述:
在上面的公式中有一個函式,它的定義如下:
(1)若,那麼
(2)若,均為互異素數,那麼
(3)其它情況下
對於函式,它有如下的常見性質:
(1)對任意正整數有
(2)對任意正整數有
轉載自:點選開啟連結