ABC 125 Person Editorial

开始补AtCoder的数学题了,练下思惟ios

AB两道都很简单,看懂题就OK。算法

C,D稍微麻烦一些ide

Problem C: GCD On Blackboard

为了解决此问题,咱们须要了解最大公约数(GCD)的某些属性。
第一点是,GCD是可交换的(GCD(a,b)= GCD(b,a))。
第二个是3个数字的GCD是关联的(GCD(a,GCD(b,c))= GCD(GCD(a,b),c))。
最后,在不失通常性的前提下,若是a和b是2个正数且a <= b,则GCD(a,b)<= a。 换句话说,2个数字的GCD最多等于较小的数字。spa

回到问题陈述,咱们有一个数字列表,能够修改列表中的一个数字,而后用另外一个数字替换它,以使列表的GCD最大化。 让咱们将关于GCD的已知知识应用于此问题:code

假设咱们的列表 \(A = A_1,A_2,A_3,...,A_n\),咱们但愿修改索引 \(A_i\)。 根据关联规则,咱们知道:GCD(A1,A2,A3,...,Ai,...,An)能够从新排序为:GCD(A1,A2,A3,...,An,Ai)。 换句话说,咱们能够计算除 \(A_i\)之外的全部数字的GCD,而后使用\(A_i\)计算结果的GCD的结果。 令g为除\(A_i\)之外的全部数字的GCD。 如今咱们但愿最大化GCD(g,Ai)。 因为对\(A_i\)进行简化后咱们能够得到的最大GCD为g,所以答案为g。排序

所以,咱们知道在位置i处可得到的最大gcd是除\(A_i\)之外的全部数字的gcd。 若是咱们在每一个 \(i\) 处计算这些gcd值,则全部结果的最大值是修改列表中的数字后可能的最大GCD。
那么咱们将如何实现该算法? 为方便起见,让咱们定义\(R_i\)\(L_i\)的两个功能,以下所示:索引

\[R_{n + 1} = 0\\R_i = GCD(R_{i + 1},A_i)\\L_0= 0\\L_i = GCD(L_{i - 1},A_i) \]

\(L_i\)表明A1…Ai的gcd,\(R_i\)表明Ai…An的GCD。 若是咱们但愿找到除Ai之外的全部元素的GCD,则能够找到Mi = GCD(Li-1,Ri + 1)。 这表示除Ai之外的全部数字的GCD。
咱们能够预先计算R和L。最后,咱们能够找到Mi的每一个值,而后计算出最大值,这就是咱们的答案。 示例代码以下(复杂度为\(O(n)\)):ip

const int N = 100000 + 5;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    // L_i = gcd(A_1, A_2, A_3, ... A_i)
    // R_i = gcd(A_i, A_{i+1}, A_{i+1}, ..., A_n)
    int n;
    ll a[N], L[N], R[N];
    cin >> n;

    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        L[i] = R[i] = a[i];
    }

    // precomputing L[i]
    for (int i = 1; i <= n; i++) {
        L[i] = __gcd(L[i], L[i - 1]);
    }
    // precomputing R[i]
    for (int i = n; i > 0; i--) {
        R[i] = __gcd(R[i], R[i + 1]);
    }

    // computing each m_i and the max value
    int res = 0;
    for (int i = 1; i <= n; i++) {
        int m = __gcd(L[i - 1], R[i + 1]);
        res = max(m, res);
    }

    cout << res;
    return 0;
}

Problem D: Flipping Signs

让咱们考虑元素A1,A2,A3,…,An的列表。 咱们被容许翻转任意两个连续元素Ai和Ai + 1的符号。 须要注意的一个有用观察结果是(除了最后一个元素),咱们能够翻转任何Ai的符号而不会影响Ai-1。
可是咱们如何利用这个事实呢? 好吧,咱们知道咱们能够使从i = 1到i = n-1的每一个元素对于任何列表都是正的。 因为咱们不在意操做数量,所以这种贪婪的方法效果很好。
给定任何数字A列表,咱们将可以使用此方法使除最后一个元素以外的全部Ai均为正。 最后一个元素将为负或正。 让咱们分别处理这种状况。
因此如今咱们能够将最后一个元素设为正数或负数。 若是为正,则全部元素均为正,列表的总和就是咱们的答案。 若是它是负数,那么咱们须要检查一些变化以得到最大的总和结果。 首先,咱们须要检查最后两个元素中哪一个更大。 因为咱们能够使最后一个元素为正,而使An-1为负。 实际上,咱们能够使任何一个Aj为负(j <n),以使最后一个元素为正。 考虑三个连续的正a,b,c元素。 咱们首先能够翻转a和b的符号,这将使它们变为负数。 在下一步中,咱们能够翻转b的符号,这将使b变为正,而c变为负。 这将使a变为负数,咱们能够再次翻转c的符号使其变为正数,依此类推。 咱们能够连接此操做,该操做将只留下a为负数,其他元素保持正值(最后一个元素除外,该元素将从负数变为正数)。
所以,咱们有了算法最后一步的配方:咱们检查范围A1…An-1中的最小元素。 将此最小元素称为m。 若是m <An,则将m的符号翻转为负,而后使An为正。 不然,咱们将m保持为正。 这个清单的总和就是咱们的答案。
Bellow是此解决方案的C ++实现。 注意,该解决方案与列表的其他部分分开处理An-1,An的状况,可是它与上述算法相同。ci

using ll = long long;
const int N = 1e5 + 10;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n, A[N];
    // store the sum of the result in a long long pair
    ll s = 0;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> A[i];

    for (int i = 1; i <= n - 2; ++i) {
        if (A[i] < 0) A[i] *= -1, A[i + 1] *= -1;
        s += A[i];
    }

    // if n is 2, we only need to consider those
    // Also , if the last 2 elements are either both positive
    // or both negative then we simply flip their signs too
    if ((n == 2) or (A[n] > 0 and A[n - 1] > 0) or (A[n] < 0 and A[n - 1] < 0))
        s += max(A[n - 1] + A[n], -A[n - 1] - A[n]);
    else {
        // find the min element in the list (excluding last 2 elements)
        int* mni = min_element(A + 1, A + n - 2);
        int mnv = *mni;

        // if the minimum value in the remaining list is larger than
        // the smaller value of the last 2 elements, then we should
        // just keep it positive
        if (mnv > min(abs(A[n - 1]), abs(A[n]))) {
            s += max(A[n - 1] + A[n], -A[n - 1] - A[n]);
        }
        // otherwise, we can make that value negative, and include the
        // last 2 elements as positive elements
        else {
            s -= mnv * 2;
            s += abs(A[n - 1]);
            s += abs(A[n]);
        }
    }
    cout << s << "\n";

    return 0;
}
相关文章
相关标签/搜索