莫比乌斯反演入门题目
整除分块
首先解决一个整除分块的问题。考虑的值,从到。会发现有大量的使得有相同的值。例如,当取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;
}