有趣的游戏
题目描述
游戏在一棵大小为 \(n\) 的树上进行。其中每个点都有点权,第 \(i\) 个点的点权为 \(w_i\)。
每一次系统会给出一条链,小 A 可以从这条链上找出两个点权不同的点 \(x,y\),他的得分是 \(w_x\bmod w_y\)。然后小 B 会从整棵树中选取两个小 A 没有选过的点,计分方式同小 A。
为了保持游戏难度,系统有时会增加一个点的权值。
当然,小 A 会尽可能使自己得分最大,他想知道这个值是多少。同时,他想知道,在自己得分最大的情况下,小 B 的最大得分是多少。
思路:
根据取模的性质可以知道, 如果 \(w_x < w_y\) ,那 \(w_x \bmod w_y = w_x\) ,否则 $ \left(w_x \bmod w_y \right) < w_y \ \left(w_x \geq w_y\right)$ 。所以如果要让所选出来的数相互取模之后尽可能的大,就要让两数中较小的那一个数作为 \(w_x\) 较大的数作为 \(w_y\) 。所以只需要知道每一次链中的最大值和严格次大值,以及剩下的整棵树中的最大值和严格次大值就行了。那么链中的最大值和严格次大值,可以用 树链剖分后建出的线段树来维护。
// 树链剖分
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";
}
}
标签:std,return,P6157,int,Luogu,树链,ff,siz,son
From: https://www.cnblogs.com/Haven-/p/16782478.html