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;
}
相關文章
相關標籤/搜索