📜  GCD为1的最小子数组|段树(1)

📅  最后修改于: 2023-12-03 14:41:22.182000             🧑  作者: Mango

GCD为1的最小子数组 | 段树

什么是最小子数组?

最小子数组是指在一个数组中,连续的一段子数组中的元素和最小。

GCD是什么?

GCD(最大公约数)是两个或多个整数的公共因数中最大的那个数。在数学上,GCD用于简化分数的计算,也用于计算最小公倍数等。

问题描述

给定一个数组,找到GCD为1的最小子数组的长度。

解决方案

为了方便处理,可以将数组中的所有数字先取模。问题变为在取模后的数组中找到GCD为1的最小子数组的长度。

解法一:暴力枚举

首先,可以暴力枚举所有的子数组,然后判断这个子数组的GCD是否为1,如果是,则更新最小长度。时间复杂度为$O(n^3)$。

代码:

int calc_gcd(int a, int b) {
    if (b == 0) return a;
    else return calc_gcd(b, a % b);
}

int find_min_subarray(int* arr, int n) {
    int min_len = n;
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            int sub_gcd = arr[i];
            for (int k = i + 1; k <= j; k++) {
                sub_gcd = calc_gcd(sub_gcd, arr[k]);
            }
            if (sub_gcd == 1) {
                min_len = min(min_len, j - i + 1);
            }
        }
    }
    return min_len;
}
解法二:线段树

由于需要求解多个子数组的GCD,可以使用线段树来优化。线段树的每个节点维护一个区间的GCD,查询时可以快速得到任意一个子数组的GCD。

可以定义一个新的结构体来存储每个节点的信息:

struct node_t {
    int gcd;
};

对于每个节点,他的GCD可以根据他的左右子节点的GCD来计算得到。具体计算方式是求左右子节点GCD的最大公约数。

代码:

struct node_t {
    int gcd;
};

class SegmentTree {
   public:
    SegmentTree(int* arr_, int n) : arr(arr_) {
        tree_size = 1;
        while (tree_size < n) tree_size <<= 1;
        tree = new node_t[tree_size * 2];
        build(1, 0, n - 1);
    }
    ~SegmentTree() {
        delete[] tree;
    }

    int query(int left, int right) {
        node_t result = query_range(1, 0, tree_size - 1, left, right);
        return result.gcd;
    }

   private:
    int* arr;
    int tree_size;
    node_t* tree;

    void build(int node, int left, int right) {
        if (left == right) {
            tree[node].gcd = arr[left];
            return;
        }
        int mid = left + (right - left) / 2;
        build(node * 2, left, mid);
        build(node * 2 + 1, mid + 1, right);
        tree[node].gcd = __gcd(tree[node * 2].gcd, tree[node * 2 + 1].gcd);
    }

    node_t query_range(int node, int left, int right, int range_left, int range_right) {
        if (range_left <= left && right <= range_right) {
            return tree[node];
        }
        int mid = left + (right - left) / 2;
        node_t result = {0};
        if (range_left <= mid) {
            result = query_range(node * 2, left, mid, range_left, range_right);
        }
        if (range_right > mid) {
            node_t right_result = query_range(node * 2 + 1, mid + 1, right, range_left, range_right);
            if (result.gcd == 0) {
                result = right_result;
            } else if (right_result.gcd != 0) {
                result.gcd = __gcd(result.gcd, right_result.gcd);
            }
        }
        return result;
    }
};

int find_min_subarray(int* arr, int n) {
    int* mod_arr = new int[n];
    for (int i = 0; i < n; i++) {
        mod_arr[i] = arr[i] % 2 == 0 ? arr[i] / 2 : arr[i];
    }

    SegmentTree st(mod_arr, n);
    int result = n;
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            int sub_gcd = st.query(i, j);
            if (sub_gcd == 1) {
                result = min(result, j - i + 1);
                break;
            }
        }
    }
    return result == n ? -1 : result;
}
总结

GCD为1的最小子数组问题可以使用线段树来优化,时间复杂度为$O(n^2 \log n)$。虽然比暴力要快,但是依旧不是最优解,还有进一步的优化空间。

参考资料