Дано дерево из n вершин. На каждой вершине дерева...
Создано: 10 февраля 2026 г.
Создано: 10 февраля 2026 г.
Дано дерево из n
вершин. На каждой вершине дерева записано число; на i
-й вершине записано число ai
.
Напомним, что простой путь — это путь, посещающий каждую вершину не более одного раза. Назовем весом пути побитовое исключающее ИЛИ значений записанных на его вершинах. Скажем, что дерево хорошее, если в нем не существует ни одного простого пути с весом 0
.
Вы можете применить следующую операцию произвольное количество раз (возможно, ноль): выбрать вершину дерева и заменить значение записанное на ней на произвольное положительное целое число. Какое минимальное количество раз необходимо применить данную операцию, чтобы дерево стало хорошим?
Входные данные
В первой строке задано целое число n
(1≤n≤2⋅105
) — количество вершин.
Во второй строке задано n
целых чисел a1
, a2
, ..., an
(1≤ai<230
) — числа, записанные на вершинах дерева.
Затем следует n−1
строка, каждая из которых содержит два числа x
и y
(1≤x,y≤n;x≠y
), обозначающие ребро между вершиной x
и вершиной y
. Гарантируется, что эти ребра задают дерево.
Выходные данные
Выведите одно целое число — минимальное количество раз, которое необходимо применить операцию, чтобы дерево стало хорошим.
что не так?
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
template<class T> using oset = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
#define ll long long
#define int ll
void solve(){
int n;
cin >> n;
vector<int>val(n + 1);
for (int i = 1; i <= n; i++)
cin >> val[i];
vector<vector<int> >g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int ans = 0;
vector<set<int> > st(n + 1);
vector<int>xr(n + 1);
function<void(int, int)> dfs = [&](int u, int p) {
st[u].insert(0);
xr[u] ^= val[u];
bool bad = false;
for (int v : g[u]) {
if (v != p) {
dfs(v, u);
if (st[u].size() < st[v].size()) {
st[u].swap(st[v]);
swap(xr[u], xr[v]);
}
for (int vl : st[v]) {
if (st[u].find(vl ^ xr[v] ^ xr[u]) == st[u].end()) {
st[u].insert(vl ^ xr[v] ^ xr[u] ^ val[u]);
} else {
bad = true;
}
}
}
}
if (bad) {
ans++;
st[u].clear();
}
};
dfs(1, 1);
cout << ans;
}
int32_t main() {
ios_base::sync_with_stdio(0); cin.tie(0);
srand(time(0));
text#ifdef DEBUG freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #else
// freopen("floor.in", "r", stdin);
// freopen("floor.out", "w", stdout);
#endif
int tests = 1;
// cin >> tests;
while (tests--) {
solve();
}
return 0;
}
У тебя в коде сломана сама “смысловая” величина, которую ты хранишь в xr[] и в st[].
xr[u] у тебя не является префиксным XORТы делаешь:
cppxr[u] ^= val[u];
но не передаёшь XOR от родителя. В итоге xr[u] после DFS почти всегда просто val[u] (или мусор от swap’ов), а не XOR по пути от корня/от текущей вершины. Отсюда все формулы вида vl ^ xr[v] ^ xr[u] становятся бессмысленными.
Если ты хотел префикс от корня, должно быть что-то вроде:
cppxr[u] = xr[p] ^ val[u];
(или передавать текущий XOR параметром).
Правильная DSU-логика для этой задачи обычно такая:
u храним множество st[u] значенийu до x, не включая u (для x в поддереве u).u условие “вес = 0” превращается в:
где t_* — элементы из st[u].При мердже ребёнка v в u нужно преобразовать все значения из v:
если в st[v] лежит XOR от v вниз, то относительно u это будет
(t ^ a[v]) (потому что путь от u вниз проходит через v и включает a[v]).
И проверка конфликта:
для каждого x = t ^ a[v] надо проверить, есть ли в текущем st[u] значение x ^ a[u].
st[u].clear() недостаточноПосле “починки” вершины u (ты увеличиваешь ans) поддерево выше должно видеть, что от u “торчат” только нулевой путь длины 0 (то есть u как точка).
Поэтому нужно делать:
cppst[u].clear(); st[u].insert(0);
(у тебя вставка 0 есть только в начале DFS, но после clear её нет).
cppfunction<void(int,int)> dfs = [&](int u, int p){ st[u].clear(); st[u].insert(0); // t(u)=0 (путь от u до u, не включая u) bool bad = false; for(int v: g[u]) if(v!=p){ dfs(v,u); if(st[u].size() < st[v].size()) st[u].swap(st[v]); // переносим значения из v к u: x = t ^ a[v] for(int t: st[v]){ int x = t ^ val[v]; // хотим найти y в st[u], что x ^ y == a[u] <=> y == x ^ a[u] if(st[u].count(x ^ val[u])) bad = true; st[u].insert(x); } st[v].clear(); } if(bad){ ans++; st[u].clear(); st[u].insert(0); // после замены значения в u считаем, что наружу “выходит” только u } };
xr[] не считается как нужно (нет накопления от родителя).xr[u], xr[v], val[u] без корректной интерпретации.st[u].clear() ты теряешь базовый 0, из-за чего наверху начинает ломаться логика.Если хочешь — могу прямо на твоём исходнике сделать минимальный diff (сохранить set<int>, твой DSU swap и т.п.), чтобы он стал корректным и проходил ограничения 2e5.
у меня в сете хранится какие числа у меня могут быть
Да, я понял идею: в set ты хранишь “какие XOR-значения могут получиться на путях вниз”.
Но проблема в том, что ты неправильно пересчитываешь эти значения при подъёме вверх.
st[u]Если ты хочешь DSU-on-tree, то удобно хранить так:
st[u]= все значения XOR на путях отuдо вершиныxв его поддереве, но безa[u].
Тогда:
0 всегда есть (путь u -> u, пустой по вершинам кроме u).v дал значение t (XOR от v до x без a[v]),u это будет t ^ a[v].Плохой путь с LCA = u существует, если есть две вершины x и y из разных поддеревьев такие что:
Если st[u] хранит XOR от u вниз без a[u], то условие становится:
То есть для каждого t_x надо проверить наличие t_y = t_x ^ a[u].
Ты пытаешься делать это через:
cppvl ^ xr[v] ^ xr[u]
но:
xr[u] у тебя не означает XOR путиТы делаешь:
cppxr[u] ^= val[u];
но не учитываешь родителя → это почти всегда просто val[u], а не путь.
Поэтому ты складываешь какие-то случайные числа, которые не являются XOR путей.
Ты думаешь:
"в set лежат возможные значения, значит я их как-то XOR-ну и получу новые"
Но пересчёт должен быть строго:
v к u:cppnew_value = old_value ^ val[v];
А проверка конфликта:
cppif (st[u] contains (new_value ^ val[u])) -> bad
Ты делаешь:
cppif (bad) { ans++; st[u].clear(); }
Но после очистки надо вернуть базу:
cppst[u].clear(); st[u].insert(0);
иначе родитель получит пустое множество и всё развалится.
Твой set — нормальная идея, но:
xr[] вообще не нуженvl ^ xr[v] ^ xr[u] ^ val[u] неверны^ val[child]{0}Если хочешь, я могу переписать твой код в том же стиле (DSU swap + set) так, чтобы он был корректным и проходил 2e5.