模拟赛-经典游戏

经典游戏

https://uoj.ac/problem/719

这题是今年北大集训原题

首先考虑题目里的经典问题怎么做,很明显对于每个棋子独立。然后就是对每个棋子求 \(\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);
}
posted @ 2022-08-02 13:58  siriehn_nx  阅读(51)  评论(0编辑  收藏  举报