Реши на C++ за O(n): У Little X есть n различных ц...
Creado el: 24 de febrero de 2025
Creado el: 24 de febrero de 2025
Реши на C++ за O(n):
У Little X есть n различных целых чисел: p1, p2, ..., pn. Он хочет так разделить все числа на два множества, A и B, чтобы выполнялись условия:
Если число x принадлежит множеству A, то число a - x также должно принадлежать множеству A.
Если число x принадлежит множеству B, то число b - x также должно принадлежать множеству B.
Помогите Little X разделить числа на два множества или определите, что это невозможно.
Входные данные
В первой строке записано три целых числа через пробел n, a, b (1 ≤ n ≤ 105; 1 ≤ a, b ≤ 109). В следующей строке записано n различных целых чисел через пробел p1, p2, ..., pn (1 ≤ pi ≤ 109).
Выходные данные
Если способ разделения чисел на два множества существует, то выведите "YES" в первой строке. Затем выведите n целых чисел: b1, b2, ..., bn (bi равняется либо 0, либо 1), описывающих разделение. Если bi равняется 0, то pi принадлежит множеству A, в противном случае оно принадлежит множеству B.
Если это невозможно, выведите "NO" (без кавычек).
Примеры
Входные данныеСкопировать
4 5 9
2 3 4 5
Выходные данныеСкопировать
YES
0 0 1 1
Входные данныеСкопировать
3 3 4
1 2 4
Выходные данныеСкопировать
NO
Анализ задачи
Даны различных целых чисел . Нужно разбить их все на два подмножества и так, чтобы выполнялись условия:
Причём каждое из данных чисел должно быть либо в , либо в (никаких «неиспользованных» не остаётся).
Однако из формулировки в задаче часто пропускается критическая деталь:
«Если , то должно принадлежать множеству »
означает не только «если уже среди наших чисел, то оно тоже обязано быть в », но и косвенно накладывает запрет класть в , если не встречается в списке из чисел. Ведь мы можем раскладывать только данные чисел по множествам, а «требовать» в число, которого в исходном наборе нет, невозможно.
Иными словами:
Если для какого-то оба варианта (положить в или в ) невозможны, значит задачу решить нельзя. Либо же при попытках удовлетворить все «цепочки» условий возникнет противоречие.
Примеры из условия
textn=4, a=5, b=9 p = [2, 3, 4, 5]
Посмотрим, кто может попасть в :
Посмотрим, кто может попасть в :
Получается, что:
Это действительно согласовано и даёт разбиение:
В ответе выводят:
textYES 0 0 1 1
(если нумерация p
идёт как [2,3,4,5], то 0
означает «в A», 1
— «в B»).
textn=3, a=3, b=4 p = [1, 2, 4]
Для :
Следовательно, обязан оказаться в .
Для :
На первый взгляд мог бы пойти куда угодно, но поскольку уже обязан в , а « в тоже в », то это заставляет тоже быть в .
Для :
Итого, не получается положить ни в , ни в . Поэтому задача не имеет решения, ответ «NO».
Итоговая проверка «можно ли распределить»
Для каждого числа из входного набора проверяем, какие варианты (0 = «A», 1 = «B») вообще возможны.
Далее нужно учесть, что если (по ходу решения) вдруг « точно пошёл в », это автоматически заставляет тоже быть в , и так далее каскадом. Аналогично для . Если эти каскадные требования вступают в противоречие, то решения нет.
Если для каких-то чисел выбор не жёстко фиксирован (им можно и в , и в ), мы можем выбрать любой из вариантов, но с учётом связей «в паре» (при выборе обязательно « в » и т.п.). Если каскад не даёт противоречий, то решение существует.
Для реализации за удобно воспользоваться следующей схемой:
Сначала подготовим:
p
.indexOf
, чтобы быстро по значению находить индекс в p
.possible[i]
(например, в виде битовой маски), который говорит, куда может пойти . Изначально possible[i] = 0b11
(т.е. оба варианта разрешены).0b01
(то есть «в » нельзя). Если не в наборе, убираем «бит» 0b10
(то есть «в » нельзя). Если после этого possible[i] == 0
, немедленно выводим «NO» и завершаемся.Далее фиксируем «жёстко определившиеся» элементы, для которых осталась ровно одна возможность (possible[i] == 0b01
или possible[i] == 0b10
). Складируем их в очередь и обрабатываем по принципу «широкого поиска»:
По ходу этого процесса либо выйдет конфликт, либо мы «сжав» все связи получим подмножество элементов, у которых осталось по одному варианту. Те элементы, у которых и в конце остались оба варианта или , мы можем свободно (без противоречий) выбрать как или . Обычно достаточно «пробежаться» и всем им присвоить, скажем, (если оно у них действительно в possible[i]
), сделав аналогичную финальную «протяжку» по связям. В этой задаче не нужно искать все способы, достаточно любого корректного.
Если в итоге все получили допустимые цвета без конфликта, печатаем «YES» и раскладку (0/1). Если по пути — «NO», выводим «NO».
Этот метод укладывается в – (все связи — максимум по 2 «партнёра» на каждый : и ).
Ниже приведён вариант кода, реализующий описанную идею.
cpp#include <bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int n; long long a, b; cin >> n >> a >> b; vector<long long> p(n); for(int i=0; i<n; i++){ cin >> p[i]; } // Сохраним в словарь "значение -> индекс" // Чтобы быстро находить, где лежит a-x или b-x unordered_map<long long,int> indexOf; indexOf.reserve(n); indexOf.max_load_factor(0.7f); for(int i=0; i<n; i++){ indexOf[p[i]] = i; } // possible[i] будет битовой маской: // 0b01 (единица) - можно ли i-й элемент положить в A, // 0b10 (двойка) - можно ли i-й элемент положить в B. // Изначально ставим 0b11 (оба варианта), // потом будем вычеркивать невозможное. vector<int> possible(n, 0b11); // Предварительно обрежем те варианты, где a-x или b-x не существует в наборе // Если a-x не найдём, убираем бит "A" // Если b-x не найдём, убираем бит "B" for(int i=0; i<n; i++){ long long x = p[i]; // Проверка на возможность "x в A" // Нужно, чтобы (a - x) был в словаре if(!indexOf.count(a - x)){ // Невозможно x в A possible[i] &= ~0b01; // вычёркиваем бит A } // Проверка на возможность "x в B" // Нужно, чтобы (b - x) был в словаре if(!indexOf.count(b - x)){ // Невозможно x в B possible[i] &= ~0b10; // вычёркиваем бит B } // Если оказалось, что вариантов нет, сразу NO if(possible[i] == 0){ cout << "NO\n"; return 0; } } // Очередь для "жёстко зафиксированных" элементов // Будем хранить пары (индекс, color), где color=0 для A, color=1 для B. queue<pair<int,int>> q; // Если у кого-то один бит остался, сразу добавляем в очередь auto pushForced = [&](int i){ // Если possible[i] == 1 (бит 0b01) => color=0 // Если possible[i] == 2 (бит 0b10) => color=1 // Если 3 - оба варианта, пока не трогаем if(possible[i] == 1){ q.push({i,0}); } else if(possible[i] == 2){ q.push({i,1}); } }; for(int i=0; i<n; i++){ if(possible[i] == 1 || possible[i] == 2){ pushForced(i); } } // Функция, которая "заставляет" элемент i идти в цвет c (0 или 1), // и сразу обрезает вариативность. Если конфликт - вернёт false. auto fixColor = [&](int i, int c){ // c=0 => бит A=0b01, c=1 => бит B=0b10 int bit = (1 << c); // Если у i уже не включает bit, значит конфликт if((possible[i] & bit) == 0){ return false; } // Иначе "обрезаем" всё, кроме нужного бита if(possible[i] != bit){ possible[i] = bit; // Добавим в очередь, чтобы отработать связанные q.push({i, c}); } return true; }; // Выполним обход в ширину, отрабатывая "жёсткие" приписки while(!q.empty()){ auto [i, c] = q.front(); q.pop(); long long x = p[i]; if(c == 0){ // x пошёл в A => (a - x) тоже должен пойти в A long long y = a - x; int j = indexOf[y]; // по условию есть // Заставляем j быть тоже в A if(!fixColor(j, 0)){ cout << "NO\n"; return 0; } } else { // x пошёл в B => (b - x) тоже должен пойти в B long long y = b - x; int j = indexOf[y]; if(!fixColor(j, 1)){ cout << "NO\n"; return 0; } } } // Если ещё остались элементы possible[i] == 3 (оба варианта), // мы можем свободно выбрать им, например, A (color=0) – // и если это где-то далее вызовет фиксации, тоже прогоняем. for(int i=0; i<n; i++){ if(possible[i] == 3){ // Попробуем зафиксировать в A (c=0), // это точно не даст противоречий, // ведь если "можно и то, и другое", // то выбирать A – законно. // Но важно запустить fixColor, чтобы цепочки дозаполнились. if(!fixColor(i, 0)){ // если вдруг конфликт (что маловероятно, // т.к. мы проверили "possible[i] == 3"), // тогда была бы опция fixColor(i,1). // Но с учётом логики — не должно здесь быть конфликта. cout << "NO\n"; return 0; } // Запустим цикл обработки, пока все цепочки не улягутся while(!q.empty()){ auto [ii, cc] = q.front(); q.pop(); long long xx = p[ii]; if(cc == 0){ long long yy = a - xx; int jj = indexOf[yy]; if(!fixColor(jj,0)){ cout << "NO\n"; return 0; } } else { long long yy = b - xx; int jj = indexOf[yy]; if(!fixColor(jj,1)){ cout << "NO\n"; return 0; } } } } } // Если дошли сюда — решение есть. cout << "YES\n"; // Выведем раскладку в том же порядке, что и входные p[i]. // У нас: possible[i] будет 1 (значит "A") или 2 (значит "B"). // Нужно вывести 0 для A, 1 для B: for(int i=0; i<n; i++){ if(possible[i] == 1){ cout << 0 << " "; // A } else { cout << 1 << " "; // B } } cout << "\n"; return 0; }
Пояснения к коду:
Мы сначала сохраняем в possible[i]
все допустимые варианты для .
Далее все элементы, у которых осталась ровно одна возможность (т.е. possible[i] == 1
или possible[i] == 2
), мы складываем в очередь q
как жёстко зафиксированные.
В цикле while(!q.empty())
берём «зафиксированный» элемент . Если он зафиксирован в , то и обязан быть в . Если у изначально была возможность (B) — её теперь нужно исключить. Если окажется, что у вообще больше нет вариантов, значит противоречие. Если у после «обрезания» остался ровно один вариант, мы тоже добавляем в очередь и так рекурсивно/каскадно. Аналогично с .
После полного «пропихивания» очереди может остаться часть элементов, у которых possible[i] == 3
, т.е. они по-прежнему «могут и в , и в ». Для них мы делаем выбор (скажем, попробуем «в »), и если это порождает новые фиксации, опять пойдёт очередь. Если возникнет противоречие, теоретически можно было бы попробовать «в », но в данной задаче (как показывает анализ) конфликт в таком месте не возникает, поскольку possible[i] == 3
означает, что и оба лежат в наборе, и цепочки таких «двусторонних» вершин не приведут к тупику — мы всегда можем выбрать любую сторону, и всё согласуется (если не возникало обязательств от соседей).
Если в итоге конфликтов не обнаружилось, мы печатаем «YES» и конечную раскладку.
Таким образом за один-полтора прохода (плюс очередь) всё раскладывается за . Если где-то по пути ловим противоречие — «NO».
Это ровно соответствует примеру, где во втором тесте ( ) моментально выяснится, что для элемента изначально possible[..]==0
(оба бита выкидываются), и мы печатаем «NO». А в первом тесте ( ) получаем ровное распределение.
Итог: