[BZOJ - 2819] Nim 【树链剖分 / DFS序】
题目链接: BZOJ - 2819
题目分析
我们知道,单纯的 Nim 的必胜状态是,各堆石子的数量异或和不为 0 。那么这道题其实就是要求求出树上的两点之间的路径的异或和。要求支持单点修改。
方法一:树链剖分
这道题用树链剖分显然是可以做的,并且也很好写。
我刚开始写完之后又 WA 了,又是线段树写错了!!这次是建树的时候写错了!
Warning!Warning!
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 500000 + 5; int n, m, Index; int A[MaxN], Num[MaxN], Father[MaxN], Depth[MaxN], Size[MaxN], Top[MaxN], Son[MaxN], Pos[MaxN]; int T[MaxN * 4]; struct Edge { int v; Edge *Next; } E[MaxN * 2], *P = E, *Point[MaxN]; inline void AddEdge(int x, int y) { ++P; P -> v = y; P -> Next = Point[x]; Point[x] = P; } int DFS_1(int x, int Dep, int Fa) { Depth[x] = Dep; Father[x] = Fa; Size[x] = 1; int SonSize, MaxSonSize; SonSize = MaxSonSize = 0; for (Edge *j = Point[x]; j; j = j -> Next) { if (j -> v == Fa) continue; SonSize = DFS_1(j -> v, Dep + 1, x); if (SonSize > MaxSonSize) { MaxSonSize = SonSize; Son[x] = j -> v; } Size[x] += SonSize; } return Size[x]; } void DFS_2(int x) { if (x == 0) return; if (x == Son[Father[x]]) Top[x] = Top[Father[x]]; else Top[x] = x; Pos[x] = ++Index; Num[Pos[x]] = A[x]; DFS_2(Son[x]); for (Edge *j = Point[x]; j; j = j -> Next) { if (j -> v == Father[x] || j -> v == Son[x]) continue; DFS_2(j -> v); } } void Build_Tree(int x, int s, int t) { if (s == t) { T[x] = Num[s]; return; } int m = (s + t) >> 1; Build_Tree(x << 1, s, m); Build_Tree(x << 1 | 1, m + 1, t); T[x] = T[x << 1] ^ T[x << 1 | 1]; } void Change(int x, int s, int t, int a, int b) { if (s == t) { T[x] = b; return; } int m = (s + t) >> 1; if (a <= m) Change(x << 1, s, m, a, b); else Change(x << 1 | 1, m + 1, t, a, b); T[x] = T[x << 1] ^ T[x << 1 | 1]; } int Query(int x, int s, int t, int l, int r) { if (l <= s && r >= t) return T[x]; int m = (s + t) >> 1; int ret = 0; if (l <= m) ret ^= Query(x << 1, s, m, l, r); if (r >= m + 1) ret ^= Query(x << 1 | 1, m + 1, t, l, r); return ret; } bool EQuery(int x, int y) { int fx, fy, Temp; Temp = 0; while (true) { fx = Top[x]; fy = Top[y]; if (Depth[fx] < Depth[fy]) { swap(fx, fy); swap(x, y); } if (fx == fy) { if (Pos[x] < Pos[y]) Temp ^= Query(1, 1, n, Pos[x], Pos[y]); else Temp ^= Query(1, 1, n, Pos[y], Pos[x]); break; } else { Temp ^= Query(1, 1, n, Pos[fx], Pos[x]); x = Father[fx]; } } if (Temp != 0) return true; return false; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &A[i]); int a, b; for (int i = 1; i <= n - 1; ++i) { scanf("%d%d", &a, &b); AddEdge(a, b); AddEdge(b, a); } DFS_1(1, 0, 0); Index = 0; DFS_2(1); Build_Tree(1, 1, n); scanf("%d", &m); char ch; for (int i = 1; i <= m; ++i) { ch = '#'; while (ch != 'C' && ch != 'Q') ch = getchar(); scanf("%d%d", &a, &b); if (ch == 'C') Change(1, 1, n, Pos[a], b); else { if (EQuery(a, b)) printf("Yes\n"); else printf("No\n"); } } return 0; }
方法二:DFS序
我们可以维护每个点 x 到根节点的路径的异或和 f(x),那么对于从 a 点到 b 点的路径,我们先求出 a 和 b 的 LCA。那么答案就是 f(a) ^ f(b) ^ A[LCA(a, b)] 。因为在 f(a) 与 f(b) 中, f(LCA(a, b)) 其实没有被算入答案(因为抑或了两次就抵消了),所以再抑或一次将其补上。
对于每次的单点修改,只会影响它的子树的 f 值,所以就可以树状数组搞一下?
我想知道的是..这个样例为何这么神奇..不管有什么离谱的错误都能过样例...简直可怕..
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 500000 + 15, MaxLog = 22; int n, m, Index, MaxT; int Pos1[MaxN], Pos2[MaxN], Depth[MaxN], Father[MaxN], A[MaxN], T[MaxN * 2], Jump[MaxN][MaxLog + 3]; struct Edge { int v; Edge *Next; } E[MaxN * 2], *P = E, *Point[MaxN]; inline void AddEdge(int x, int y) { ++P; P -> v = y; P -> Next = Point[x]; Point[x] = P; } inline void Change(int x, int Num) { for (int i = x; i <= MaxT; i += i & -i) T[i] ^= Num; } inline int Get(int x) { int ret = 0; for (int i = x; i; i -= i & -i) ret ^= T[i]; return ret; } void DFS(int x, int Dep, int Fa) { Father[x] = Fa; Depth[x] = Dep; Pos1[x] = ++Index; Change(Pos1[x], A[x]); for (Edge *j = Point[x]; j; j = j -> Next) { if (j -> v == Fa) continue; DFS(j -> v, Dep + 1, x); } Pos2[x] = ++Index; Change(Pos2[x], A[x]); } void Prepare_LCA() { for (int i = 1; i <= n; ++i) Jump[i][0] = Father[i]; for (int j = 1; j <= MaxLog; ++j) for (int i = 1; i <= n; ++i) Jump[i][j] = Jump[Jump[i][j - 1]][j - 1]; } int LCA(int x, int y) { int Dif; if (Depth[x] < Depth[y]) swap(x, y); Dif = Depth[x] - Depth[y]; if (Dif) { for (int i = 0; i <= MaxLog; ++i) if (Dif & (1 << i)) x = Jump[x][i]; } if (x == y) return x; for (int i = MaxLog; i >= 0; --i) { if (Jump[x][i] != Jump[y][i]) { x = Jump[x][i]; y = Jump[y][i]; } } return Father[x]; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &A[i]); int a, b; for (int i = 1; i <= n - 1; ++i) { scanf("%d%d", &a, &b); AddEdge(a, b); AddEdge(b, a); } MaxT = n * 2 + 5; Index = 0; DFS(1, 0, 0); Prepare_LCA(); scanf("%d", &m); char ch; int Temp; for (int i = 1; i <= m; ++i) { ch = '#'; while (ch != 'C' && ch != 'Q') ch = getchar(); scanf("%d%d", &a, &b); if (ch == 'C') { Change(Pos1[a], A[a]); Change(Pos2[a], A[a]); A[a] = b; Change(Pos1[a], A[a]); Change(Pos2[a], A[a]); } else { Temp = Get(Pos1[a]) ^ Get(Pos1[b]) ^ A[LCA(a, b)]; if (Temp != 0) printf("Yes\n"); else printf("No\n"); } } return 0; }