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

文章目录

  • 题目
  • 分析
  • 代码

题目

[AtCoder Petrozavodsk Contest 001F] XOR Tree

分析

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

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

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

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

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

代码

#include 
#include 
#include 
#include 

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

你可能感兴趣的:([,错题本,],#,状压,DP,异或,动态规划,状态压缩)