模拟赛-经典游戏
经典游戏
这题是今年北大集训原题
首先考虑题目里的经典问题怎么做,很明显对于每个棋子独立。然后就是对每个棋子求 \(\text{sg}\) 函数,然后异或起来。
一个棋子的 \(\text{sg}\) 函数就是这个节点所在子树的高度。特别的,一个节点的高度为 \(0\)。
然后考虑这个小 \(K\) 加一个棋子的操作是要干什么,这玩意就是要对于我们当前局面再加上一个数字。当前的根是 \(x\),令 \(w_x\) 表示以 \(x\) 为根的最深深度。就是说小 \(K\) 可以让当前局面异或上 \(0\sim w_x\) 之间的一个数字。也就是说,假设不挪根的话只有 \(w_x < \text{xor}\) 的情况小 \(C\) 是赢的。
小 \(C\) 是要干什么,它可以挪根,他只要挪到一个这种节点就赢了。需要求能多少种挪法。
假设根在 \(x\),一个节点处有奇数个棋子的时候是对于整个局面有贡献的,应该贡献了 \(t_{x, y}\) 的值,其中 \(t_{x, y}\) 表示 \(x\) 为根 \(y\) 节点的最深子树深度。
考虑长链剖分和dfs,这样可以快速的求出来一个节点到其他点的最远距离(这里我们需要求出来在 \(x\) 子树内的最深儿子和次深儿子),然后求 \(t_{x, y}\) 的时候讨论 \(y\) 是在 \(x\) 的子树外,还是 \(y\) 的长儿子里,还是在 \(y\) 的短儿子们里。然后就可以维护 \(ans_x\) 表示 \(x\) 为根的时候的局面的函数值。
发现对一个点加一个棋子其实就是对于 \(ans\) 数组做四个区间异或。你就可以支持 \(O(\log n)\) 的修改,同时查询就是遍历整个点的所有相邻节点就可以了,每次查询是 \(O(\deg)\) 的。
发现 \(x\) 和 \(x\) 的短儿子被做的修改特别相近,就是说在不修改短儿子本身的情况下他们被异或的数字完全相同。那么我们可以使用数据结构来维护 \(x\) 所有短儿子的答案。每次修改 \(x\) 的时候如果是短儿子就修改这结构即可。
查询是相当于是每次给这些短儿子 \(a_i\) 全体异或上 \(k\),求有多少个 \(i\) 满足 \(a_i > w_i\),这玩意可以用 Trie 树来做。我们对于每个点可以求出来他当前被异或的数字 \(u_x\),然后短儿子被异或的数字 \(u_i\),然后对于一个 \(w_i\) 为 \(0\) 的位置,如果我们最后的 \(a_i\) 这一位是 \(1\),并且 \(w_i\) 和 \(a_i\) 前面位都一样就可以被加到答案里去。我们加入儿子 \(i\) 的时候 \(x\) 节点异或的值是 \(u_x\),查询的时候变成了 \(u_x'\) 这就说明 \(a_i\) 也应该异或上了 \(u_x \oplus u_x'\)。
考虑如何在 Trie 树上做这个问题,在加入数字的时候,我们插入 \(w_i \oplus u_x \oplus u_i\),然后在路径上满足这一位 \(w_i\) 是 \(0\) 的节点的父亲的另外一个儿子处打上标记。
我们查询的时候用 \(u_x'\) 在这个 Trie 树上走统计路径节点的所有标记和即可,原因是能走到这个打标记节点,说明他们之前的位全都一样,然后这一位上 \(w_i\) 是 \(0\),同时 \(a_i = u_x \oplus u_i \oplus u_x'\) 这一位上是 \(1\)。
总时间复杂度为 \(O((N + M)\log N)\),空间复杂度为 \(O((N + M)\log N)\)
// Siriqwq
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::sort;
using std::get;
using std::unique;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
using ll = long long;
namespace qwq {
mt19937 eng;
void init(int Seed) {return eng.seed(Seed);}
int rnd(int l = 1, int r = 1000000000) {return uniform_int_distribution<int> (l, r)(eng);}
}
template<typename T>
inline void chkmin(T &x, T y) {if (x > y) x = y;}
template<typename T>
inline void chkmax(T &x, T y) {if (x < y) x = y;}
template<typename T>
inline T min(const T &x, const T &y) {return x < y ? x : y;}
template<typename T>
inline T max(const T &x, const T &y) {return x > y ? x : y;}
char buf[100000], *bufs, *buft;
#define gc() ((bufs == buft && (buft = (bufs = buf) + fread(buf, 1, 100000, stdin))), bufs == buft ? -1 : *bufs++)
template<typename T>
inline void read(T &x) {
x = 0;
bool f = 0;
char ch = gc();
while (!isdigit(ch)) f = ch == '-', ch = gc();
while (isdigit(ch)) x = x * 10 + ch - '0', ch = gc();
if (f) x = -x;
}
inline void reads(char *s) {
char ch = gc();
while (isspace(ch)) ch = gc();
while (!isspace(ch) && ch != EOF) *(s++) = ch, ch = gc();
*s = 0;
return;
}
template<typename T, typename ...Arg>
inline void read(T &x, Arg &... y) {
read(x);
read(y...);
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 1048576, MOD = 998244353, inv2 = (MOD + 1) / 2, I32_INF = 0x3f3f3f3f;
const long long I64_INF = 0x3f3f3f3f3f3f3f3f;
auto Ksm = [] (int x, int y) -> int {
if (y < 0) {
y %= MOD - 1;
y += MOD - 1;
}
int ret = 1;
for (; y; y /= 2, x = (long long) x * x % MOD) if (y & 1) ret = (long long) ret * x % MOD;
return ret;
};
auto Mod = [] (int x) -> int {
if (x >= MOD) return x - MOD;
else if (x < 0) return x + MOD;
else return x;
};
template<const int N_num, const int M_num>
struct Graph {
int H[N_num];
struct Edge {int to, lac;} e[M_num];
inline void add_edge(int x, int y) {e[*H] = {y, H[x]};H[x] = (*H)++;}
inline void init() {memset(H, -1, sizeof H);*H = 0;}
};
#define go(x, y) for (int i = x.H[y], v; (v = x.e[i].to) && ~i; i = x.e[i].lac)
inline int ls(int k) {return k << 1;}
inline int rs(int k) {return k << 1 | 1;}
using ull = unsigned long long;
void add(int &x, int y) {if ((x += y) >= MOD) x -= MOD;}
int Type, N, M, A[MAXN], h[MAXN], h2[MAXN], up[MAXN], fa[MAXN], sz[MAXN], son[MAXN], clk, dfn[MAXN], w[MAXN];
Graph<MAXN, MAXN * 2> tr;
struct FenWick_Tree {
int sc[MAXN];
void add(int x, int y) {
for (; x <= N; x += x & -x) sc[x] ^= y;
}
void add(int l, int r, int x) {
if (l > r) return;
add(l, x);
add(r + 1, x);
}
int qry(int x) {
int r = 0;
for (; x; x -= x & -x) r ^= sc[x];
return r;
}
} c9;
void mfy(int x) {
// [h[x], w[x], h[x]] -> three segments
c9.add(1, dfn[x] - 1, h[x]);
c9.add(dfn[x], dfn[x], w[x]);
c9.add(dfn[x] + sz[x], N, h[x]);
if (son[x]) {
c9.add(dfn[son[x]], dfn[son[x]] + sz[son[x]] - 1, max(up[x], h2[x]));
c9.add(dfn[x] + sz[son[x]] + 1, dfn[x] + sz[x] - 1, w[x]);
}
}
int ncnt, ch[MAXN * 40][2], ct[MAXN * 40];
struct Trie {
int rt;
void ins(int x, int k, int o) {
// ins x ^ k
// k = 0 and x = 1 ans += o
// 查询的时候给一个 y, 询问 x ^ y 和 k 的大小
// 我们插入 x ^ k, 然后贡献就是 k = 0, 并且 x ^ y = 1 的时候。
// 就是 y = x ^ 1 的时候
// 一样当且仅当 x ^ k = y
if (!rt) rt = ++ncnt;
int nw = rt;
for (int i = 19; ~i; --i) {
int X = (x >> i) & 1, K = (k >> i) & 1, nxt = K ^ X;
if (!K) {
if (!ch[nw][nxt ^ 1]) ch[nw][nxt ^ 1] = ++ncnt;
ct[ch[nw][nxt ^ 1]] += o;
}
if (!ch[nw][nxt]) ch[nw][nxt] = ++ncnt;
nw = ch[nw][nxt];
}
}
int qry(int y) {
int nw = rt, ans = 0;
for (int i = 19; ~i && nw > 0; --i) {
nw = ch[nw][(y >> i) & 1];
ans += ct[nw];
// assert(ct[nw] >= 0);
}
return ans;
}
} ti[MAXN];
int lv[MAXN];
void upd(int x) {
// light son
if (fa[x] && son[fa[x]] != x) {
if (~lv[x]) ti[fa[x]].ins(lv[x], w[x], -1);
lv[x] = c9.qry(dfn[fa[x]]) ^ c9.qry(dfn[x]);
ti[fa[x]].ins(lv[x], w[x], 1);
}
}
int qryp(int x) {return c9.qry(dfn[x]) > w[x];}
int qry(int x) {
int ans = qryp(x);
// O(ans);
if (fa[x]) ans += qryp(fa[x]);
if (son[x]) ans += qryp(son[x]);
ans += ti[x].qry(c9.qry(dfn[x]));
return ans;
}
int main() {
freopen("classic.in", "r", stdin);
freopen("classic.out", "w", stdout);
// std::ios::sync_with_stdio(0);
// cout << std::fixed << std::setprecision(8);
// cin.tie(0);
// cout.tie(0);
qwq::init(20050217);
read(Type, N, M);
tr.init();
for (int i = 1, x, y; i < N; ++i) {
read(x, y);
tr.add_edge(x, y);
tr.add_edge(y, x);
}
// O(1);
auto dfs1 = [&] (auto self, int u) -> void {
sz[u] = 1;
// O(u);
go(tr, u) if (v != fa[u]) {
fa[v] = u;
self(self, v);
sz[u] += sz[v];
if (h[v] + 1 >= h[u]) {
h2[u] = h[u];
h[u] = h[son[u] = v] + 1;
} else chkmax(h2[u], h[v] + 1);
}
};
dfs1(dfs1, 1);
auto dfs2 = [&] (auto self, int u) -> void {
dfn[u] = ++clk;
if (son[u]) up[son[u]] = max(h2[u], up[u]) + 1, self(self, son[u]);
go(tr, u) if (v != fa[u] && v != son[u]) {
up[v] = max(h[u], up[u]) + 1;
self(self, v);
}
};
dfs2(dfs2, 1);
// O(1);
for (int i = 1; i <= N; ++i) w[i] = max(h[i], up[i]);
for (int i = 1; i <= N; ++i) {
read(A[i]);
if (A[i] & 1) mfy(i);
}
memset(lv, -1, sizeof lv);
for (int i = 1; i <= N; ++i) {
upd(i);
// O(w[i]);
// O(up[i]);
// O(h[i]);
// O(h2[i]);
}
for (int i = 1, x, y; i <= M; ++i) {
read(x, y);
mfy(x);
upd(x);
printf("%d\n", qry(y));
}
// cout << (-3 / 2);
cerr << ((double) clock() / CLOCKS_PER_SEC) << '\n';
return (0-0);
}