容斥原理 求[1..m]中与m不互质的数的个数

//容斥原理的实现

//1.队列数组

//2.dfs

//3.二进制表示

//队列数组是枚举所有情况,可能有时候空间上存不下?dfs的优点是用时间换空间


ps:

1.int fac[15]

//13! > 1e8,所以质因子的个数小于15 //long long 的话21! > ull,25肯定够

2.二进制

注意空集不要算

//3.如果要求[1..n]中与m不互质的数的个数,即二进制枚举子集,求子集所有数字的lcm,n / lcm即可

1.队列数组

m = p1 ^ x1 * p2 ^ x2 * ... * pk ^ xk,求小于等于m的,整除p1或p1或...pk的数的个数


#include <iostream>

#include <cstdio>

#include <cmath>

using namespace std;

const int maxn =1e5 +5;

int q[maxn],fac[15],num;//队列数组q,fac记录m的质因子,num记录质因子个数


void divide(int m)

{

    num = 0;

    for (int i =2; i <=sqrt(0.5 + m); i ++) {

        if(m % i == 0){

            fac[num ++] = i;

            while(m % i == 0) m /= i;

        }

        if(m == 1)break;

    }

    if(m != 1)fac[num ++] = m;

}

int solve(int m)//小于等于m中,和m不互质的数的个数

{

    int sz = 0;

    q[sz ++] = -1;

    for (int i =0; i <num; i ++) {

        int k = sz;

        for (int j =0; j < k; j ++) {

            q[sz ++] = -1 *fac[i] *q[j];

        }

    }

    //计算A并B并C...个数

    int ans = 0;

    for (int i =1; i < sz; i ++) {

        ans += m / q[i];   //m = 2 * 3 * 5,n / q[i]分别等于15,10,6,-5,-3,-2,1,即各个块的大小,可以用他们的个数求别的

    }

    return ans;

}

int main()

{

    int m;

    while (scanf("%d",&m) !=EOF) {

        divide(m);

        int ans = solve(m);

        printf("%d\n",ans);

    }

    return 0;

}

//2.dfs

#include <iostream>

#include <cstdio>

#include <cmath>

using namespace std;

typedef long long ll;

int fac[15],num;

int m;


void divide(int m)

{

    num = 0;

    for (int i =2; i <=sqrt(0.5 + m); i ++) {

        if(m % i == 0){

            fac[num ++] = i;

            while(m % i == 0) m /= i;

        }

        if(m == 1)break;

    }

    if(m != 1)fac[num ++] = m;

}

//其实本质上就是枚举所有情况,和二进制枚举没啥区别

//pos为当前质因子的位置,cnt为当前公共因子的个数,搜索含有c个公共因子的数的个数

int p[15];

int tmp = 0;

void dfs(int pos,int cnt,int c)

{

    if(cnt == c){

        int x = m;

        for (int i =1; i <= c; i ++) {

            x /= p[i];//计算个数

        }

        tmp += x;

        return;

    }

    for (int i = pos; i <num; i ++) {

        p[cnt + 1] =fac[i];//记录这一次枚举了谁,放在p中

        dfs(i + 1 , cnt +1, c);

    }

}

int main()

{


    while (scanf("%d",&m) !=EOF) {

        divide(m);

        ll ans = 0;

        for (int i =1; i <=num; i ++) {

            tmp = 0;

            dfs(0,0, i);

            if(i & 1) ans += tmp;

            else ans -= tmp;

        }

        printf("%lld\n",ans);

    }

    return 0;

}

//3.二进制

#include <cstdio>

#include <iostream>

#include <cmath>

using namespace std;

typedef long long ll;

int fac[15],num;


void divide(int m)

{

    num = 0;

    for (int i =2; i <=sqrt(0.5 + m); i ++) {

        if(m % i == 0){

            fac[num ++] = i;

            while(m % i == 0) m /= i;

        }

        if(m == 1)break;

    }

    if(m != 1)fac[num ++] = m;

}

ll solve(int m)

{

    ll ans = 0;

    for (int i =1; i < (1 <<num); i ++) {//空集不算,否则会多减一个m

        int t = i,x = m,cnt = 0;

        for (int j =0; j <num; j ++) {

            if(t & 1) {x /= fac[j];cnt ++;}

            t >>= 1;

            if(t == 0) break;

        }

        if(cnt & 1) ans += x;

        else ans -= x;

    }

    return ans;

}

int main()

{

    int m;

    while (scanf("%d",&m) !=EOF) {

        divide(m);

        ll ans = solve(m);

        printf("%lld\n",ans);

    }

    return 0;

}





猜你喜欢

转载自blog.csdn.net/sm_545/article/details/80024477