Трійка#12369

Трійка (Розбір)

Автор задачі:

Андрій Холод (Connect)

Підготував:

Андрій Холод (Connect)

Розбір:

Андрій Холод (Connect)

Ідея

На другому етапі ми можемо лише стискати підвідрізки в їхню суму. Це означає, що фінальний масив [x,x,x] завжди відповідає деякому розбиттю початкового масиву на три непорожні суцільні частини з однаковими сумами.

Тобто після всіх замін на першому етапі масив має стати таким, щоб існували індекси 1 \le i < j < n і виконувалось:

\sum_{k=1}^{i} a_k = \sum_{k=i+1}^{j} a_k = \sum_{k=j+1}^{n} a_k

Отже задача насправді така: змінити мінімум елементів, щоб масив можна було розбити на три непорожні підвідрізки з однаковими сумами.

Чому відповідь не більша за 2

Завжди можна змінити не більше двох елементів.

Справді, візьмемо будь-яке розбиття на три непорожні частини, наприклад: { {a_1}, {a_2}, {a_3,\dots,a_n} } Тоді можна змінити один елемент у першій частині і один елемент у другій частині так, щоб суми всіх трьох частин стали однаковими.

Тому відповідь завжди одна з: [ 0,\ 1,\ 2 ]

Отже достатньо навчитися перевіряти:

  • чи відповідь дорівнює ;

  • чи відповідь дорівнює ;

  • інакше відповідь .

Випадок 0 змін

Нехай S = \sum_{i=1}^{n} a_i

Якщо ми не змінюємо жодного елемента, то потрібно, щоб масив уже можна було розбити на три частини з однаковою сумою. Отже обов’язково: S \equiv 0 \pmod 3 і кожна частина повинна мати суму x = S/3

Через префіксні суми це перевіряється дуже просто:

  • шукаємо префікс із сумою ;

  • далі правіше шукаємо префікс із сумою .

Якщо обидва існують так, що третя частина непорожня, то відповідь .

Коли достатньо однієї зміни

Нехай ми хочемо змінити рівно один елемент. Тоді після цієї зміни масив має розбитись на три частини з однаковими сумами.

Ключове спостереження: якщо змінюється лише один елемент, то він лежить рівно в одній з трьох частин. Отже дві інші частини вже спочатку мають однакові суми. Саме третю частину ми можемо "дотягнути" однією зміною до цього значення.

Тому треба перевірити, чи існує розбиття на три непорожні частини, де суми двох частин уже однакові.

Нехай другий розріз стоїть після позиції , де 2 \le j < n Тоді три частини такі: { {1..i},\ {i+1..j},\ {j+1..n} } для деякого i < j.

Розглянемо три варіанти рівності двох частин.

1) Рівні перша і друга частини

Тоді: \text{pref}[i] = \text{pref}[j] - \text{pref}[i] тобто 2 \cdot \text{pref}[i] = \text{pref}[j] або \text{pref}[i] = \frac{\text{pref}[j]}{2}

Значить для фіксованого достатньо знати, чи зустрічався раніше префікс і чи парне.

2) Рівні друга і третя частини

Тоді: \text{pref}[j] - \text{pref}[i] = S - \text{pref}[j] Звідси: \text{pref}[i] = 2 \cdot \text{pref}[j] - S

Отже треба перевірити, чи існував раніше такий префікс.

3) Рівні перша і третя частини

Тоді: \text{pref}[i] = S - \text{pref}[j]

Тобто теж достатньо перевірити, чи був такий префікс раніше.

Чому цих перевірок достатньо

Для кожного ми розглядаємо всі можливі i < j неявно через множину вже побачених префіксних сум.

Множина seen містить усі значення: \text{pref}[1], \text{pref}[2], \dots, \text{pref}[j-1]

Тому кожна з трьох умов вище за відповідає на питання:

  • чи існує такий , що перша і друга частини рівні;

  • чи існує такий , що друга і третя частини рівні;

  • чи існує такий , що перша і третя частини рівні.

Якщо хоч одна з цих умов виконується, то дві частини вже мають однакову суму, а третю можна виправити однією заміною.

Отже відповідь дорівнює .

Чому ітерація йде саме по j = 2 \dots n-1

Ми хочемо, щоб усі три частини були непорожні:

  • перша частина має містити хоча б один елемент \Rightarrow i \ge 1;

  • друга частина має бути непорожня \Rightarrow i < j;

  • третя частина має бути непорожня \Rightarrow j < n.

Тому зовнішній цикл іде по 2 \le j < n а в seen перед перевіркою додається \text{pref}[j-1], щоб індекс точно був меншим за .

Підсумковий алгоритм

Для кожного тесту:

  1. Рахуємо префіксні суми.

  2. Перевіряємо, чи можна одразу отримати три рівні суми без змін. Якщо так, відповідь .

  3. Інакше перебираємо другу межу і через множину префіксів перевіряємо три умови для однієї зміни. Якщо хоч одна виконується, відповідь .

  4. Інакше відповідь .

Коректність

Ми вже довели:

  • якщо існує розбиття на три рівні суми без змін, код виведе ;

  • якщо це неможливо, але існує розбиття, де дві частини вже мають однакову суму, код знайде його і виведе ;

  • якщо не підходить ні , ні , то завжди можна досягти відповіді за зміни.

Отже алгоритм завжди знаходить мінімальну кількість змін.

Асимптотика

Для кожного тестового випадку:

  • обчислення префіксних сум — ;

  • перевірка випадку — ;

  • перевірка випадку через unordered_set — в середньому.

Отже сумарна складність: на тест у середньому, а по всіх тестах: O\left(\sum n\right)

Пам’ять:

Код рішення

#include <bits/stdc++.h>
#include <random>
// fast_pragmas

//#pragma GCC optimize("O3")
//#pragma GCC optimize("Ofast")
//#pragma GCC optimize ("unroll-loops")
//#pragma GCC target("avx,avx2,tune=native")

// fast_pragmas ended

//#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC optimize("O3,unroll-loops")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4")
//#pragma GCC target("avx2,bmi,bmi2,popcnt,lzcnt")

using namespace std;

#define ull unsigned long long
#define ll long long
#define ld long double
#define speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define endl '\n'
#define yes cout<<"YES"<<endl
#define no cout<<"NO"<<endl
#define pb push_back
#define ln log
#define lg log10
#define log log2
#define here cout<<"HERE"<<endl

const ll clasik = 5e5 + 7;
const ll mln = 1e6 + 7;
const ll mod = 1e9 + 7;
//const ll mod = 998244353;
const ll block_size = 450;
const ll INF = 1e17;

ll df[clasik];

void solve() {
    ll n;
    cin >> n;

    vector<ll> a(n + 1), pref(n + 1, 0);

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

    ll S = pref[n];

    bool zero = false;

    if (S % 3 == 0) {
        ll x = S / 3;
        bool first_found = false;

        for (int i = 1; i < n; i++) {
            if (pref[i] == x) first_found = true;
            if (pref[i] == 2 * x && first_found) {
                zero = true;
                break;
            }
        }
    }

    if (zero) {
        cout << 0 << endl;
        return;
    }

    bool one = false;
    unordered_set<ll> seen;
    seen.reserve(n * 2);

    for (int j = 2; j < n; j++) {
        seen.insert(pref[j - 1]);

        if (pref[j] % 2 == 0 && seen.count(pref[j] / 2)) {
            one = true;
            break;
        }

        if (seen.count(2 * pref[j] - S)) {
            one = true;
            break;
        }

        if (seen.count(S - pref[j])) {
            one = true;
            break;
        }
    }

    if (one) cout << 1 << endl;
    else cout << 2 << endl;
}

signed main() {

    //freopen("deciding_points_input.txt" , "r" , stdin);
    //freopen("outputA.txt" , "w" , stdout);

    speed;
    int test;
    test = 1;
    //precalc();
    cin >> test;
    for (int tt = 1; tt <= test; tt++) {
        //cout << "Case #" << tt << ": ";
        solve();
    }

    return 0;
}