莫比烏斯反演入門題目
整除分塊
首先解決一個整除分塊的問題。考慮的值,從到。會發現有大量的使得有相同的值。例如,當取100時,從51到100都將得到相同的。所以當要求
時,完全不需要循環100次,只需要依次算出所有相同的值,相乘相加即可。
再考慮給一個數組,要求
此時可以預先求出數組的前綴和,同樣分段相乘相加即可。例如此時計算從51到100,就不需要循環50次,只需要計算
即可
洛谷P2568: GCD
給定整數N,求[1, N]範圍內有滿足gcd(x, y)爲質數的數對一共有多少對。N在以內。
參照這裏基礎問題2的推演,答案就是
其中就是題目給定的數。如果求出莫比烏斯函數的前綴和,第二個求和記號就可以使用整除分塊來計算,然後對每一個質數循環即可。
#include <stdio.h>
typedef long long int llt;
int const SIZE = 10000007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(miu(i))
void sieveEulerMobius(){
int tmp;
for(int i=2;i<SIZE;++i){
if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;
for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
isComp[tmp=i*P[j]] = true;
if ( 0 == i % P[j] ){
Euler[tmp] = Euler[i] * P[j];
Mobius[tmp] = 0;
break;
}else{
Euler[tmp] = Euler[i] * ( P[j] - 1 );
Mobius[tmp] = - Mobius[i];
}
}
C[i] = C[i-1] + Mobius[i];
}
}
int main(){
sieveEulerMobius();
int n;
scanf("%d",&n);
llt ans=0LL;
for(int i=0;i<PCnt&&P[i]<=n;++i){//對每一個質數
for(int r,j=1,nn=n/P[i];j<=nn;j=r+1){//整除分塊
r = nn / (nn/j);
ans += ( C[r] - C[j-1] ) * (nn/j) * (nn/j);
}
}
printf("%lld\n",ans);
return 0;
}
另外一種使用歐拉函數的做法,結合這裏基礎問題1和2的推導,答案還可以寫成
簡單推理一下,是不大於與互質的數量,則就是互質的數量,將其乘二就得到所有互質的對數,其中只有計算了兩次,減去即可。
#include <stdio.h>
typedef long long int llt;
int const SIZE = 10000007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(phi(i))
void sieveEulerMobius(){
int tmp;
for(int i=2;i<SIZE;++i){
if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;
for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
isComp[tmp=i*P[j]] = true;
if ( 0 == i % P[j] ){
Euler[tmp] = Euler[i] * P[j];
Mobius[tmp] = 0;
break;
}else{
Euler[tmp] = Euler[i] * ( P[j] - 1 );
Mobius[tmp] = - Mobius[i];
}
}
C[i] = C[i-1] + Euler[i];
}
}
int main(){
sieveEulerMobius();
int n;
scanf("%d",&n);
llt ans=0LL;
for(int i=0;i<PCnt&&P[i]<=n;++i) ans += (C[n/P[i]]<<1)-1LL;
printf("%lld\n",ans);
return 0;
}
SPOJ VLATTICE
整點立方體,從(0,0,0)到(N,N,N),問從原點看過去能看到多少個點, N在一百萬。
點(2,2,2)、(2,4,2)之類顯然都看不見,可知能看到的就是gcd(x,y,z)爲1的那些點。把上一道題擴展一下可得答案
當然這只是x、y、z全都是正整數即立方體內的解,還需要考慮其中一個座標爲0、即表面的解。表面解其實就是gcd(x,y)爲1的數量,然後再乘3即可。最後還要考慮數軸上的點,顯然只有3個。
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long int llt;
int const SIZE = 1000007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(miu(i))
void sieveEulerMobius(){
int tmp;
for(int i=2;i<SIZE;++i){
if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;
for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
isComp[tmp=i*P[j]] = true;
if ( 0 == i % P[j] ){
Euler[tmp] = Euler[i] * P[j];
Mobius[tmp] = 0;
break;
}else{
Euler[tmp] = Euler[i] * ( P[j] - 1 );
Mobius[tmp] = - Mobius[i];
}
}
C[i] = C[i-1] + Mobius[i];
}
}
int getUnsigned(){
char ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
int ret = (int)(ch-'0');
while( '0' <= ( ch = getchar() ) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
return ret;
}
int main(){
//freopen("1.txt","r",stdin);
sieveEulerMobius();
int nofkase = getUnsigned();
for(int kase=1;kase<=nofkase;++kase){
int n=getUnsigned();
llt ans=0LL;
//體內的點
for(int r,i=1;i<=n;i=r+1){//整除分塊
r = min(n/(n/i),n/(n/i));
ans += ( C[r] - C[i-1] ) * (n/i) * (n/i) * (n/i);
}
//一面的點
llt ans2 = 0LL;
for(int r,i=1;i<=n;i=r+1){//整除分塊
r = min(n/(n/i),n/(n/i));
ans2 += ( C[r] - C[i-1] ) * (n/i) * (n/i);
}
//再加3個座標軸上的點
printf("%lld\n",ans+ans2*3LL+3LL);
}
return 0;
}
hdu1695: GCD
在,在,問有多少對滿足。 、不要求秩序, 實際上就是問的數量。令,且設,參見這裏基礎問題3,答案就是
當然,這裏要求不計秩序,也就是(5,7)和(7,5)認爲是沒有區別的,所以要去重。很顯然,只有當y落在[1, n]範圍內纔有可能重複,所以重複的數量其實就是[1, n]中互質的數量的一半。
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long int llt;
int const SIZE = 100007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0LL,1LL};//Ci = sigma(miu(i))
void sieveEulerMobius(){
int tmp;
for(int i=2;i<SIZE;++i){
if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;
for(int j=0;j<PCnt&&i*P[j]<SIZE;++j){
isComp[tmp=i*P[j]] = true;
if ( 0 == i % P[j] ){
Euler[tmp] = Euler[i] * P[j];
Mobius[tmp] = 0;
break;
}else{
Euler[tmp] = Euler[i] * ( P[j] - 1 );
Mobius[tmp] = - Mobius[i];
}
}
C[i] = C[i-1] + Mobius[i];
}
}
int getUnsigned(){
char ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
int ret = (int)(ch-'0');
while( '0' <= ( ch = getchar() ) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
return ret;
}
int main(){
//freopen("1.txt","r",stdin);
sieveEulerMobius();
int nofkase = getUnsigned();
for(int kase=1;kase<=nofkase;++kase){
int a=getUnsigned();
int b=getUnsigned();
int c=getUnsigned();
int d=getUnsigned();
int k=getUnsigned();
if(!k){
printf("Case %d: 0\n",kase);
continue;
}
int n=b/k;
int m=d/k;
if(n>m) swap(n,m);
llt ans=0LL;
//計算出所有互異的數對,但題目要求不計秩序,還要減掉一部分
for(int r,i=1;i<=n;i=r+1){//整除分塊
r = min(n/(n/i),m/(m/i));
ans += ( C[r] - C[i-1] ) * (n/i) * (m/i);
}
//要減去的部分就是[1~n]的數對
llt ans2 = 0LL;
for(int r,i=1;i<=n;i=r+1){
r = n / (n/i);
ans2 += ( C[r] - C[i-1] ) * (n/i) * (n/i);
}
printf("Case %d: %lld\n",kase,ans-(ans2>>1));
}
return 0;
}
BZOJ 2005: 能量採集
給定n和m,假定,本質上就是問
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long int llt;
int const SIZE = 100007;//TODO
bool isComp[SIZE] = {false};
int P[SIZE] = {0};
int PCnt = 0;
int Euler[SIZE] = {0,1};
int Mobius[SIZE] = {0,1};
llt C[SIZE] = {0,1};
void sieveEulerMobius(){
llt tmp;
for(int i=2;i<SIZE;++i){
if ( !isComp[i] ) P[PCnt++] = i, Euler[i] = i - 1, Mobius[i] = -1;
for(int j=0;j<PCnt&&(tmp=i*P[j])<SIZE;++j){
isComp[tmp] = true;
if ( 0 == i % P[j] ){
Euler[tmp] = Euler[i] * P[j];
Mobius[tmp] = 0;
break;
}else{
Euler[tmp] = Euler[i] * ( P[j] - 1 );
Mobius[tmp] = - Mobius[i];
}
}
C[i] = C[i-1] + Mobius[i];
}
}
int main(){
sieveEulerMobius();
int n,m;
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
llt ans=0LL;
for(int g=1;g<=n;++g){
int nn = n/g;
int mm = m/g;
llt cnt = 0LL;
for(int r,j=1;j<=nn;j=r+1){
r = min(nn/(nn/j),mm/(mm/j));
cnt += (C[r]-C[j-1]) * (nn/j) * (mm/j);
}
ans += cnt * ( g + g - 1);
}
printf("%lld\n",ans);
return 0;
}