尋找整數(容斥原理) - HDU 1796

        看這題之前先複習一下容斥原理,不然肯定看不懂,呃,如果第一次接觸容斥原理的題,可能弄懂了容斥原理你還是看不懂代碼,是的,等會你就知道了。

        

        容斥原理簡介:在計數時,爲了使重疊部分不被重複計算,人們研究出一種新的計數方法:先不考慮重疊的情況,把包含於某內容中的所有對象的數目先計算出來,然後再把計數時重複計算的數目排斥出去,使得計算的結果既無遺漏又無重複,這種計數方法稱爲容斥原理

        

        兩個集合的容斥原理:

n(A∪B)=n(A)+n(B) -n(A∩B)

 

        要計算幾個集合並集的大小,我們要先將所有單個集合的大小計算出來,然後去所有個集合相交的部分,再回所有個集合相交的部分,再去所有個集合相交的部分,依此類推,一直計算到所有集合相交的部分。

 

        也就是加上奇數的數量,減去偶數的數量。

 

        原理還是很好理解的,做題的時候因爲有位運算的優化,就需要多思考。

 

Problem Description

   Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided exactly by any integers in the set. For example, N=12, and M-integer set is {2,3}, so there is another set {2,3,4,6,8,9,10}, all the integers of the set can be divided exactly by 2 or 3. As a result, you just output the number 7.

        給你一個數字N,以及一個有M個整數的數組集合S,求出小於數字N的所有整數集合R,使得集合R中的這些數能被S中的數整除。

        比如N=12,集合S是{2, 3},那麼還有一個集合R{2, 3, 4, 6, 8, 9, 10},所有整數都能被2或者3整除。輸出R的元素個數。

 

Input

  There are a lot of cases. For each case, the first line contains two integers N and M. The follow line contains the M integers, and all of them are different from each other. 0<N<2^31,0<M<=10, and the M integer are non-negative and won’t exceed 20.

         多個測試用例,第一行包含數字N和集合S的元素個數M。接着輸入集合S的M個數字。N用長整型。

 

Output

  For each case, output the number.

 

Sample Input

12 2

2 3

 

Sample Output

7

 

解題思路:

1、對於數字N,用M中的每個數去整除,然後計算數量,肯定有重複的。

 

2、通過容斥原理,可以知道n(A∪B∪C...)=n(A)+n(B)+n(C)...-n(A∩B)-n(A∩C)-n(B∩C)...+n(A∩B∩C)...

 

其中A、B、C表示集合S中的M個數依次整除N所能得到的集合。

 

對於n(A∩B∩...),只需要取得最小公倍數整除N得到集合。

 

3、本題性能優化主要有3處

        一個是在一開始就去掉不需要重複計算的數字,比如N爲12的時候,如果M個數是{2,3,4},首先可以把4去掉,因爲能被4整除的肯定能被2整除。

        一個是沒有用遞歸。

        一個是用到了位運算。位運算這個比較難理解,看代碼註釋慢慢研究吧,另外可以設置一些打印語句查看整個運行過程,做其他題目也一樣,便於理解前後因果。

 

4、註釋比代碼還多,與前面那個組合的問題有點相似,數學題目就是這樣,難以理解,代碼精簡,解題的性能差距比較大。

 

源代碼:G++ 78ms HDU排名93

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<fstream>
using namespace std;

long long N;
long long s[100];
vector<long long > q;

//求最大公約數
int gcd(int a, int b)
{
    if (b == 0)
        return a;
    else
        return gcd(b, a % b);
}

long long solve(int size)
{
    long long ans = 0;
    //將符合條件的m個數,看作m位,每位是0或者是1,那麼一共有2^m種狀態
    //1<<x就是2的x次方,如1<<0是2的0次方,1<<1是2的1次方
    for (long long i = 1; i < (1 << size); i++)
    {
        long long p = 1;
        int cnt = 0;
        for (long long j = 0; j < size; j++)
        {
            //與操作確定該數字是否參與組合
            //比如這裏的size爲3,那麼二進制(1<<size)-1表示爲111
            //i的值用二進制表示依次爲1,10,11,100,101,110,111
            //1<<j的值依次爲1,10,100
            //如果i的值爲110,則1<<j在10和100的時候&i就能大於0
            if ((1 << j)&i)
            {
                //計算最小公倍數
                //lcm(a,b) = a*b/gcd(a,b)
                p = p / gcd(p, q[j]) * q[j];
                cnt++;
            }
        }
        //cnt表示組合有幾個數,奇數次減去,偶數次加上
        //這裏爲啥是(n-1)/p,比如(n-1)=11,p=2,那麼被2分的數字肯定是(n-1)/p
        //依次爲2、4、6、8、10
        if (cnt & 1)
            ans += (N - 1) / p;
        else
            ans -= (N - 1) / p;
    }

    return ans;
}

//可以將測試數據放在文件裏面,便於調試
//fstream ifs("test.txt");
//#define cin ifs

int main()
{
    int M = 0;
    //輸入N和M
    while (cin >> N >> M)
    {
        q.clear();
        int flag = 0;
        //輸入M個數的集合S
        for (int i = 0; i < M; i++)
        {
            cin >> s[i];
        }
        //排序
        sort(s, s + M);
        //去掉重複的數,比如2和6,去掉6,注意這一步去重非常影響性能
        for (int i = 0; i < M; i++)
        {
            if (s[i] <= 0)
                continue;

            for (int j = i + 1; j < M; j++)
            {
                //是否能夠被整除,能夠被整除說明不需要參與組合計算
                //比如2和6,能被6整除的肯定能被2整除
                if (s[j] % s[i] == 0)
                    s[j] = -1;
            }
            q.push_back(s[i]);
        }
        long long ans = solve(q.size());
        cout << ans << endl;
    }

    return 0;
}

個人公衆號:ACM算法日常

專注於基礎算法的研究工作,深入解析ACM算法題,五分鐘閱讀,輕鬆理解每一行源代碼。內容涉及算法、C/C++、機器學習等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章