UVA-10325 The Lottery(二进制状态枚举)

The Sports Association of Bangladesh is in great problem with their latest lottery ‘Jodi laiga Jai’. There are so many participants this time that they cannot manage all the numbers. In an urgent meeting they have decided that they will ignore some numbers. But how they will choose those unlucky numbers!!! Mr. NondoDulal who is very interested about historic problems proposed a scheme to get free from this problem

his problem. You may be interested to know how he has got this scheme. Recently he has read the Joseph’s problem.

There are N tickets which are numbered from 1 to N. Mr. Nondo will choose M random numbers and then he will select those numbers which is divisible by at least one of those M numbers. The numbers which are not divisible by any of those M numbers will be considered for the lottery

As you know each number is divisible by 1. So Mr. Nondo will never select 1 as one of those M numbers. Now given N, M and M random numbers, you have to find out the number of tickets which will be considered for the lottery.

Input

Each input set starts with two Integers N (10 ≤ N < 2 31) and M (1 ≤ M ≤ 15). The next line will contain M positive integers each of which is not greater than N.

Input is terminated by EOF.

Output

Just print in a line out of N tickets how many will be considered for the lottery.

Sample Input

10 2

2 3

20 2

2 4

Sample Output

3

10

题目意思:

      给你m个整数,求1~n中不能被m个整数中任意一个整除的数有多少个

解题思路:

      由于对1~n中不能被m个数字中任意一个数整除的数不好求(时间复杂度太高),我们可以通过计算在1~n中不能被m个数字中任意一个数整除的数的个数为n-能被m个数整除的数的个数

      1到n,能被m中数字a整除的个数有n / a个,能被a 、b同时整除的数有n / lcm(a,b)个,能被a、b、c同时整除的数有n / lcm(lcm(a,b),c)个......由于计算过程中能被一个数整除的数的个数,我们需要使用容斥原理。不能被m个数字中任意一个数整除的个数=n-能被一个数整除的个数+能同时被两个数字整除的个数-能同时被三个数字整除的个数.......

方法一:

      DFS:依次对m个数字中的每个数进行DFS,每个数都可能与其他数同时被1~n之间的数整除

#include<iostream>
#include<cstring>

using namespace std;

typedef long long LL;

LL arr[20],n,m,res;

LL GCD(LL a,LL b) {
    if(a < b) {
        a = a + b;
        b = a - b;
        a = a - b;
    }
    LL c = a % b;
    while(c) {
        a = b;
        b = c;
        c = a % b;
    }
    return b;
}

LL LCM(LL a,LL b) {
    return (a / GCD(a,b)) * b;
}

void DFS(LL part,LL loc,LL num) {
    if(part > n || loc >= m) {
        return ;
    }
    for(LL i = loc;i < m;i++) {
        LL tmp = LCM(part,arr[i]);
        if(num & 1)
            res -= n / tmp;
        else
            res += n / tmp;
        DFS(tmp,i+1,num+1);
    }
}

int main() {
    while(~scanf("%lld%lld",&n,&m)) {
        res = n;
        for(int i = 0;i < m;i++) {
            scanf("%lld",&arr[i]);
        }
        for(int i = 0;i < m;i++) {
            res -= n / arr[i];
            DFS(arr[i],i+1,2);
        }
        printf("%lld\n",res);
    }
    return 0;
}

 方法二:

      二进制状态枚举:对于m个数我们可以使用m位二进制即2^m种情况来枚举所有的取值情况(1为取,0为不取)

      如2个数字可以由00(两个数都不能被整除)   01(能被第一个数整除,不能被第二个数整除)   10(能被第二个数整除,不能被第一个数整除)   11(能同时被第一个数和第二个数整除)四种取值情况,由于我们要求能被m个数中的数整除的个数,因此不考虑一个数都不取的状况,因此我们从01的状态开始枚举。part为之前的tmp个数的最小公倍数,当tmp为奇数时,res-=n/tmp,当tmp为偶数时res+=n/tmp

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;


LL GCD(LL a, LL b) {
    if(a < b) {
        a = a + b;
        b = a - b;
        a = a - b;
    }
    LL c = a % b;
    while(c) {
        a = b;
        b = c;
        c = a % b;
    }
    return b;
}

LL LCM(LL a, LL b) {
    return a / GCD(a,b) * b;
}

int main() {
    LL n, m, res, tmp, part, arr[20];
    while(cin >> n >> m) {
        for(int i = 0;i < m;i++)
            cin >> arr[i];
        res = n;
        for(int i = 1;i < (1 << m);i++) {       //枚举arr数组中每个数的状态(出现为1,否则为0)
            tmp = 0;                            //记录当前情况下是几个数字的最小公倍数
            part = 1;
            for(int j = 0;j < m;j++) {
                if(i & (1 << j)) {              //若在当前状态下整数arr[j]的状态为被取到,则计算当前状态下的LCM
                    part = LCM(part,arr[j]);
                    tmp++;
                }
            }
            if(tmp & 1)                         //若取的数字为奇数
                res -= n / part;
            else
                res += n / part;
        }
        cout << res << endl;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值