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

  

 

posted @ 2015-01-15 18:37  JoeFan  阅读(320)  评论(0编辑  收藏  举报