题目
[AtCoder Petrozavodsk Contest 001F] XOR Tree
分析
一条路径上的边权全部异或一个值比较恶心,于是有一个神仙转化:考虑到路径上所有非端点的度都为 ,也就是说进入一个点和出去一个点都异或了一个值,所以我们将点权设为 与它相连的边的边权异或和。容易证明所有点权均为 是所有边权均为 的充分必要条件。
必要性显然。证明充分性只需要考虑不断找到度为 的点,其对应的边边权为 ,于是可以删掉它们,不断删掉后即可证明所有边权为 。更严谨的说法是用归纳法证明。
再考虑操作:一个操作事实上是将两端点的点权异或同一个值,因为路径上的每个点都被异或了两次,相当于没有操作。
于是我们的问题转化为:有 个数 ,每次操作可以选择两个数 并让它们同时异或一个数。求最小操作次数使所有数变为 。
很显然要先操作相同的数,将它们全部变成 。这样一来每种数最多剩一个。然后考虑状压 DP,将每种数直接压入状态中。然后选出两个不同的数 ,显然要异或 或者 将它们变成 是最优的,因为如果不异或 中的一个数,这个操作没有任何意义。于是找到了一个 的算法。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int MAXN = 100000;
const int MAXA = 15;
const int INF = 0x3f3f3f3f;
int N;
int A[MAXN + 5];
int Cnt[MAXA + 5];
int Dp[(1 << (MAXA + 1)) + 5];
int Dfs(int S) {
if (Dp[S] != INF)
return Dp[S];
for (int i = 1; i <= MAXA; i++)
if ((S >> i) & 1)
for (int j = i + 1; j <= MAXA; j++)
if (((S >> j) & 1))
Dp[S] = std::min(Dp[S], Dfs(S ^ (1 << i) ^ (1 << j) ^ (1 << (i ^ j))) + 1 + ((S >> (i ^ j)) & 1));
return Dp[S];
}
int main() {
scanf("%d", &N);
for (int i = 1; i < N; i++) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
A[u + 1] ^= w, A[v + 1] ^= w;
}
for (int i = 1; i <= N; i++)
Cnt[A[i]]++;
int Ans = 0, S = 0;
for (int i = 1; i <= MAXA; i++) {
Ans += Cnt[i] / 2;
Cnt[i] %= 2;
if (Cnt[i])
S |= (1 << i);
}
memset(Dp, 0x3f, sizeof Dp);
Dp[0] = 0;
printf("%d", Dfs(S) + Ans);
return 0;
}