Luogu P6157 有趣的游戏(平衡树 + 树链剖分)

有趣的游戏

题目描述

游戏在一棵大小为 n 的树上进行。其中每个点都有点权,第 i 个点的点权为 wi

每一次系统会给出一条链,小 A 可以从这条链上找出两个点权不同的点 x,y,他的得分是 wxmodwy。然后小 B 会从整棵树中选取两个小 A 没有选过的点,计分方式同小 A。

为了保持游戏难度,系统有时会增加一个点的权值。

当然,小 A 会尽可能使自己得分最大,他想知道这个值是多少。同时,他想知道,在自己得分最大的情况下,小 B 的最大得分是多少。

思路:

根据取模的性质可以知道, 如果 wx<wy ,那 wxmodwy=wx ,否则 (wxmodwy)<wy (wxwy) 。所以如果要让所选出来的数相互取模之后尽可能的大,就要让两数中较小的那一个数作为 wx 较大的数作为 wy 。所以只需要知道每一次链中的最大值和严格次大值,以及剩下的整棵树中的最大值和严格次大值就行了。那么链中的最大值和严格次大值,可以用 树链剖分后建出的线段树来维护。

// 树链剖分 struct node { int v, nxt; }e[N * 2]; int idx, h[N]; void add(int a, int b) { e[++idx] = {b, h[a]}, h[a] = idx; } int n; int dfn[N], dep[N], siz[N], son[N], f[N], top[N], cnt = 0; int po[N], w[N]; void dfs1(int u = 1, int fa = 0) { siz[u] = 1, f[u] = fa, dep[u] = dep[fa] + 1; for (int i = h[u]; i; i = e[i].nxt) { int v = e[i].v; if (v == fa) continue; dfs1(v, u); siz[u] += siz[v]; if (siz[son[u]] < siz[v]) son[u] = v; } } void dfs2(int u = 1, int tp = 1) { dfn[u] = ++cnt; top[u] = tp; po[cnt] = w[u]; if (!son[u]) return ; dfs2(son[u], tp); for (int i = h[u]; i; i = e[i].nxt) { int v = e[i].v; if (v == f[u] || v == son[u]) continue; dfs2(v, v); } } // 线段树维护区间最大值和严格次大值 struct Info { std::pair<int, int> maxv; } tr[N << 2]; std::pair<int, int> mergemax(std::pair<int, int> a, std::pair<int, int> b) { std::pair<int, int> c; c.first = std::max(a.first, b.first); if (a.first != b.first) { c.second = std::min(a.first, b.first); c.second = std::max(c.second, std::max(a.second, b.second)); return c; } c.second = std::max(a.second, b.second); return c; } void build(int u = 1, int l = 1, int r = n) { if (l == r) return void(tr[u].maxv = {po[l], -inf}); int mid = (l + r) >> 1; build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); tr[u].maxv = mergemax(tr[u << 1].maxv, tr[u << 1 | 1].maxv); } void modify(int pos, int v, int u = 1, int l = 1, int r = n) { // 单点修改 if (l > pos || r < pos) return; if (l == r) return void(tr[u].maxv = {tr[u].maxv.first + v, -inf}); int mid = (l + r) >> 1; modify(pos, v, u << 1, l, mid); modify(pos, v, u << 1 | 1, mid + 1, r); tr[u].maxv = mergemax(tr[u << 1].maxv, tr[u << 1 | 1].maxv); } std::pair<int, int> query(int ln, int rn, int u = 1, int l = 1, int r = n) { // 区间查询 if (l > rn || r < ln) return std::make_pair(-inf, -inf); if (l >= ln && r <= rn) return tr[u].maxv; int mid = (l + r) >> 1; return mergemax(query(ln, rn, u << 1, l, mid), query(ln, rn, u << 1 | 1, mid + 1, r)); } std::pair<int, int> ask(int u, int v) { // 链上查询 std::pair<int, int> ans = {-inf, -inf}; while(top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) std::swap(u, v); ans = mergemax(ans, query(dfn[top[u]], dfn[u])); u = f[top[u]]; } if (dep[u] > dep[v]) std::swap(u, v); ans = mergemax(ans, query(dfn[u], dfn[v])); return ans; }

整棵树上的最大值和严格次大值可以考虑用平衡树来维护,这样也可以解决严格次大中涉及到的重复的问题。

//fhq-treap维护整个区间的最大值和严格次大值 std::mt19937 rnd(233); struct FHQ { int son[2]; int val, key, siz; } ff[N * 3]; int cur, root, x, y, z; int newnode(int x) { ff[++cur].val = x; ff[cur].key = rnd(); ff[cur].siz = 1; return cur; } void pull(int u) { ff[u].siz = ff[ff[u].son[0]].siz + ff[ff[u].son[1]].siz + 1; } int merge(int u, int v) { if (!u || !v) return u | v; if (ff[u].key <= ff[v].key) { ff[u].son[1] = merge(ff[u].son[1], v); pull(u); return u; } else { ff[v].son[0] = merge(u, ff[v].son[0]); pull(v); return v; } } void split(int u, int p, int& x, int& y) { if (!u) return void(x = y = 0); if (ff[u].val <= p) { x = u; split(ff[u].son[1], p, ff[u].son[1], y); } else { y = u; split(ff[u].son[0], p, x, ff[u].son[0]); } pull(u); } void insert(int a) { //插入操作 split(root, a, x, y); root = merge(merge(x, newnode(a)), y); } void del(int a) { //删除操作 split(root, a, x, z); split(x, a - 1, x, y); y = merge(ff[y].son[0], ff[y].son[1]); root = merge(merge(x, y), z); } int kth(int now, int k) { //第k大的数 while(true) { if (k <= ff[ff[now].son[0]].siz) now = ff[now].son[0]; else if (k == ff[ff[now].son[0]].siz + 1) return now; else { k -= ff[ff[now].son[0]].siz + 1, now = ff[now].son[1]; } } } int findpre(int a) { //前缀 split(root, a - 1, x, y); int res = ff[kth(x, ff[x].siz)].val; root = merge(x, y); return res; }

主体部分注意一下单点修改的时候先从平衡树中删除该节点后再加回去,查询四个最大值的时候,先将链中的最大值和严格次大值求出来,从平衡树中删除出去后再查询区间第二大。

std::cin >> n; for (int i = 1; i < n; i++) { int a, b; std::cin >> a >> b; add(a, b), add(b, a); // 连边 } for (int i = 1; i <= n; i++) { std::cin >> w[i]; insert(w[i]); // 插入平衡树 } dfs1(), dfs2(); // 树链剖分 build(); int q; std::cin >> q; for (int i = 0; i < q; i++) { int op, xx, yy; std::cin >> op >> xx >> yy; if (op == 0) { // 单点修改 del(w[xx]); w[xx] += yy; insert(w[xx]); modify(dfn[xx], yy); } else { // 查询 std::pair<int, int> res = ask(xx, yy); if (res.second == -inf) { std::cout << "-1\n"; continue; } del(res.first); del(res.second); int ret = findpre(findpre(inf)); insert(res.first); insert(res.second); std::cout << res.second << " " << ret << "\n"; } }

__EOF__

本文作者HoneyGrey
本文链接https://www.cnblogs.com/Haven-/p/16782478.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   浅渊  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示