51Nod 2456 最小約數 V2 質數

1. 題目描述

1.1. Limit

Time Limit: 1000 ms

Memory Limit: 131072 kB

1.2. Problem Description

給出nn個數,將這個nn數進行分組,分組規則爲:除了1以外最小的約數相同的數字分爲一組。最後,輸出這個最小約數,同時按照從小到大的順序逐一輸出這些數字。

例如:7個數,35 8 39 12 8 26 25
輸出爲:

2 8 8 12 26( [8, 8, 12, 26] 爲給定的7個數中,以2爲最小約數(除1以外)的數)
3 39( [39] 爲給定的7個數中,以3爲最小約數(除1以外)的數)
5 25 35( [25, 35] 爲給定的7個數中,以5爲最小約數(除1以外)的數)


1.3. Input

第一行:1個數nn1000n(n \le 1000)
後面 nn 行,每行1個數 a[i]2<=a[i]<=10000a[i](2 <= a[i] <= 10000)


1.4. Output

按照約數 dd 從小到大的順序,逐行輸出所有最小約數(除了1之外的)爲 dd 的數字,並且每行中輸出數字的順序也是從小到大。


1.5. Sample Input

7
35
8
39
12
8
26
25

1.6. Sample Output

2 8 8 12 26
3 39
5 25 35

1.7. Source

51Nod 2456 最小約數 V2


2. 解讀

這道題有兩個部分需要完成

  1. 分解最小約數
  2. 分組排序輸出

首先我們考慮分解最小約數的問題。

給定一個數 xx ,要找到其除了1以外最小的約數 dd,以此可以推斷我們要找的這個約數肯定是一個質數,因爲如果 dd 不是一個質數,那麼我們只要對其再做一次質數分解,就可以求得比它更小的一個約數。

明確了我們要找的數是一個質數以後,我們可以考慮在求出質數表以後用暴力搜索的方法來求解。結合這道題的數據範圍 [1,10000][1, 10000] 考慮,在這個範圍裏面只有1229個質數,而輸入也最多隻有1000個數,1229×1000=1.229×1061229 \times 1000 = 1.229 \times 10 ^6,也就是說在求出一萬以內的質數以後,我們最多隻要再做100多萬次運算就能求出結果。

而我們使用的求質數的埃氏篩算法的複雜度也是O(nloglogn)O(n \log\log n)的,接近O(n)O(n)複雜度,所以和搜索加起來總共也就一百多萬次運算。參考一臺普通的筆記本電腦1秒大概1千萬次的運算速度,我們是可以在1秒以內完成暴力搜索過程的。

下面對埃氏篩算法做簡單的說明,這個算法可以快速找到[2,n][2, n]以內的質數。對於初始隊列 {1,2,3,n}\{1, 2, 3, \dots n \},操作步驟如下 1

  1. 輸出最小的素數2,然後篩掉2的倍數,剩下 {3,5,7,9,11,13}\{ 3, 5, 7, 9, 11, 13\dots \}
  2. 輸出最小的質數3,然後篩掉3的倍數,剩下 {5,7,11,13}\{5, 7, 11, 13\dots \}
  3. 輸出最小的質數5,然後篩掉5的倍數,剩下 {7,11,13}\{ 7, 11, 13\dots \}
  4. 繼續以上的步驟,直到隊列爲空

然後考慮分組排序輸出的問題。

我使用的是 map<int, multiset<int>> 來存儲結果,因爲 map 能對 key 也就是最小約數進行排序,而 set 能對值排序,考慮到值可能重複,這裏使用的是可存儲重複項的 multiset

3. 代碼

#include <algorithm>
#include <iostream>
#include <map>
#include <set>
#include <string.h>

using namespace std;

const int MAXN = 1e4;

// 存儲素數
int prime[MAXN + 1];
// 標記訪問
bool visit[MAXN + 1];
// 分類存儲
map<int, multiset<int>> mp;

// 埃氏篩法,計算[2, n]內的素數
int E_sieve(int n)
{
    // 初始化
    memset(visit, 0, sizeof(visit));
    // 篩掉非素數
    for (int i = 2; i * i <= n; i++) {
        if (!visit[i]) {
            for (int j = i * i; j <= n; j += i) {
                // 標記爲非素數
                visit[j] = true;
            }
        }
    }
    // 下面記錄素數
    // 統計素數的個數
    int k = 0;
    for (int i = 2; i <= n; i++) {
        if (!visit[i]) {
            prime[k++] = i;
        }
    }
    return k;
}

int main()
{
    // 計算1e4以內的素數
    int primeMark = E_sieve(MAXN);
    // test case
    int t;
    scanf("%d", &t);
    // buffer
    int buffer;

    // 循環
    for (int i = 0; i < t; i++) {

        // 輸入
        scanf("%d", &buffer);
        // 找到其質數分解後,除1以外的的最小質數
        int j = 0;
        // 若質數小於buffer
        while (prime[j] <= buffer && j < primeMark) {
            // 找到的第一個質數
            if (buffer % prime[j] == 0) {
                if (mp.find(prime[j]) != mp.end()) {
                    // 若已存儲過
                    mp[prime[j]].insert(buffer);
                } else {
                    // 若未存儲過
                    multiset<int> st;
                    st.insert(buffer);
                    mp[prime[j]] = st;
                }
                // 退出循環
                break;
            }
            j++;
        }
    }
    // 輸出
    for (auto it = mp.begin(); it != mp.end(); it++) {
        printf("%d ", it->first);
        for (auto itSet = it->second.begin(); itSet != it->second.end(); itSet++) {
            printf("%d ", *itSet);
        }
        printf("\n");
    }
    return 0;
}

4. 參考文獻

[1] 羅勇軍, 郭衛斌. 算法競賽入門到進階 [M]. 北京: 清華大學出版社, 2019.


聯繫郵箱:[email protected]

Github:https://github.com/CurrenWong

歡迎轉載/Star/Fork,有問題歡迎通過郵箱交流。

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