結果

問題 No.1917 LCMST
ユーザー qwewe
提出日時 2025-05-14 13:12:46
言語 C++17
(gcc 13.3.0 + boost 1.87.0)
結果
AC  
実行時間 379 ms / 4,000 ms
コード長 7,902 bytes
コンパイル時間 1,147 ms
コンパイル使用メモリ 91,296 KB
実行使用メモリ 48,956 KB
最終ジャッジ日時 2025-05-14 13:14:02
合計ジャッジ時間 15,901 ms
ジャッジサーバーID
(参考情報)
judge3 / judge5
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 3
other AC * 42
権限があれば一括ダウンロードができます

ソースコード

diff #

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <tuple>
#include <limits> // Needed for numeric_limits

// Define long long type for potentially large costs
typedef long long ll;

// Define the maximum possible value for A_i based on problem constraints
const int MAX_VAL = 100005; 

// Global arrays to store counts and presence of A_i values
int count[MAX_VAL];         // Stores count of each A_i value
bool present[MAX_VAL];      // Tracks which A_i values are present in the input
std::vector<int> distinct_values; // Stores the distinct A_i values present in the input

// Disjoint Set Union (DSU) structure
struct DSU {
    std::vector<int> parent;
    // Constructor initializes DSU for elements indexed 1 to n. Array size n+1.
    DSU(int n) {
        parent.resize(n + 1);
        // Initialize each element as its own parent
        std::iota(parent.begin(), parent.end(), 0);
    }

    // Find the representative (root) of the set containing x, with path compression optimization
    int find(int x) {
        if (parent[x] == x) {
            return x;
        }
        // Path compression: set parent directly to the root
        return parent[x] = find(parent[x]);
    }

    // Unite the sets containing x and y
    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        // If they are not already in the same set, merge them
        if (rootX != rootY) {
            // Simple merge: make rootX child of rootY. Could use union by rank/size for further optimization.
            parent[rootX] = rootY; 
        }
    }
};

// Computes greatest common divisor (GCD) using Euclidean algorithm
ll gcd(ll a, ll b) {
    while (b) {
        a %= b;
        std::swap(a, b);
    }
    return a;
}

// Computes least common multiple (LCM) using the formula lcm(a, b) = (a / gcd(a, b)) * b
// This implementation assumes the final LCM result fits within a 64-bit long long,
// based on problem constraints (max A_i = 10^5, max LCM approx 10^10).
ll lcm(ll a, ll b) {
    // Handle edge case where a or b is 0
    if (a == 0 || b == 0) return 0;
    
    // Compute GCD first
    ll common_divisor = gcd(a, b);
    // If GCD is 0, should not happen for positive a, b
    if (common_divisor == 0) return 0; 
    
    // Calculate (a / gcd(a, b)) first. This reduces the magnitude before multiplication,
    // helping to prevent intermediate overflow.
    ll term1 = a / common_divisor; 
    
    // Perform the final multiplication. Potential overflow check using 128-bit integers
    // is possible with GCC/Clang (#ifdef __GNUC__ ... unsigned __int128 ...) but omitted
    // here for simplicity and assuming constraints guarantee result fits in long long.
    return term1 * b;
}


// multiples[g] will store a list of distinct values k present in the input A array such that g divides k
std::vector<int> multiples[MAX_VAL]; 

int main() {
    // Use fast I/O operations
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int N; // Number of vertices
    std::cin >> N;

    int max_A = 0; // Track the maximum A_i value encountered
    // Read input values A_i
    for (int i = 0; i < N; ++i) {
        int A_i;
        std::cin >> A_i;
        // Record presence and count of A_i
        if (!present[A_i]) { // If seeing this value for the first time
            present[A_i] = true;
            distinct_values.push_back(A_i); // Add to list of distinct values
        }
        count[A_i]++; // Increment count for this value
        // Update maximum A_i value seen so far
        if (A_i > max_A) {
            max_A = A_i;
        }
    }

    ll total_cost = 0; // Initialize total MST cost
    // Step 1: Calculate initial MST cost within groups of vertices having the same A_i value.
    // For each distinct value k present in the input:
    // All count[k] vertices with value k form a clique where edges have weight lcm(k, k) = k.
    // To connect these count[k] vertices into one component, we need count[k]-1 edges of weight k.
    for (int k : distinct_values) {
         total_cost += (ll)(count[k] - 1) * k;
    }

    // Precompute the list of multiples for each potential GCD value g up to max_A.
    // This helps efficiently find pairs (k, l) related by a common divisor g later.
    for (int g = 1; g <= max_A; ++g) {
        // Iterate through all multiples k of g up to max_A
        for (int k = g; k <= max_A; k += g) {
            // If k is one of the values present in the input A array
            if (present[k]) {
                // Add k to the list of multiples for g
                multiples[g].push_back(k);
            }
        }
    }
    
    // Stores potential edges for the MST calculation between components of distinct values.
    // Each tuple is (weight, u, v) where weight = lcm(u, v).
    std::vector<std::tuple<ll, int, int>> potential_edges;
    
    // Step 2: Generate candidate edges connecting components based on GCD property.
    // This approach is derived from a technique used in similar competitive programming problems.
    // It generates a set of edges guaranteed to contain the MST edges connecting the components.
    // Iterate through all possible GCD values g from 1 up to max_A
    for (int g = 1; g <= max_A; ++g) {
        // If there are fewer than 2 multiples of g present among the distinct A_i values,
        // we cannot form an edge based on this GCD g.
        if (multiples[g].size() < 2) continue;
        
        // Find the minimum value k_min among the multiples of g present in the input.
        // This node k_min acts as a hub for connecting other multiples of g.
        int min_k = multiples[g][0]; 
        for(size_t i = 1; i < multiples[g].size(); ++i){
             if(multiples[g][i] < min_k) min_k = multiples[g][i];
        }

        // Add potential edges connecting k_min to all other multiples k of g.
        // The edge weight is lcm(k, k_min).
        for (int k : multiples[g]) {
            if (k != min_k) { // Don't add edge from min_k to itself
                potential_edges.emplace_back(lcm((ll)k, (ll)min_k), k, min_k);
            }
        }
    }

    // Sort the generated potential edges by their weight (LCM value) in non-decreasing order.
    std::sort(potential_edges.begin(), potential_edges.end());

    // Step 3: Run Kruskal's algorithm on the components representing distinct values.
    // Use DSU structure initialized for values up to max_A.
    DSU dsu(max_A); 
    int num_distinct_values = distinct_values.size();
    int edges_count = 0; // Count number of edges added to connect the components

    // If there's only 0 or 1 distinct value type, the graph is already connected (or empty).
    // The initial cost calculated in Step 1 is the final answer.
    if (num_distinct_values > 1) {
        // Iterate through sorted potential edges
        for (const auto& edge : potential_edges) {
            ll weight;
            int u, v;
            std::tie(weight, u, v) = edge; // Unpack edge tuple
            
            // Check if the components containing u and v are already connected
            if (dsu.find(u) != dsu.find(v)) {
                // If not connected, add this edge to the MST
                dsu.unite(u, v);         // Merge the components
                total_cost += weight;    // Add the edge weight to the total MST cost
                edges_count++;           // Increment the count of edges added
                
                // We need exactly num_distinct_values - 1 edges to connect all components.
                // If we have added enough edges, stop Kruskal's algorithm.
                if (edges_count == num_distinct_values - 1) {
                     break;
                }
            }
        }
    }

    // Output the final total MST cost
    std::cout << total_cost << std::endl;

    return 0;
}
0