[AtCoder Petrozavodsk Contest 001F] XOR Tree(巧妙的转化 + 状压 DP) | 错题本

文章目录

题目

[AtCoder Petrozavodsk Contest 001F] XOR Tree

分析

一条路径上的边权全部异或一个值比较恶心,于是有一个神仙转化:考虑到路径上所有非端点的度都为 2 2 ,也就是说进入一个点和出去一个点都异或了一个值,所以我们将点权设为 与它相连的边的边权异或和。容易证明所有点权均为 0 0 是所有边权均为 0 0 的充分必要条件。

必要性显然。证明充分性只需要考虑不断找到度为 1 1 的点,其对应的边边权为 0 0 ,于是可以删掉它们,不断删掉后即可证明所有边权为 0 0 。更严谨的说法是用归纳法证明。

再考虑操作:一个操作事实上是将两端点的点权异或同一个值,因为路径上的每个点都被异或了两次,相当于没有操作。

于是我们的问题转化为:有 n n 个数 a i   ( 0 a i 15 ) a_i\ (0 \leq a_i \leq 15) ,每次操作可以选择两个数 a i , a j a_i, a_j 并让它们同时异或一个数。求最小操作次数使所有数变为 0 0

很显然要先操作相同的数,将它们全部变成 0 0 。这样一来每种数最多剩一个。然后考虑状压 DP,将每种数直接压入状态中。然后选出两个不同的数 i , j i, j ,显然要异或 i i 或者 j j 将它们变成 0 , i j 0, i \oplus j 是最优的,因为如果不异或 i , j i, j 中的一个数,这个操作没有任何意义。于是找到了一个 2 15 × 1 5 2 2^{15} \times 15^2 的算法。

代码

#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;
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/107687174