首页 > 其他分享 >Luogu P6157 有趣的游戏(平衡树 + 树链剖分)

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

时间:2022-10-11 20:47:31浏览次数:75  
标签:std return P6157 int Luogu 树链 ff siz son

有趣的游戏

题目描述

游戏在一棵大小为 \(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

相关文章

  • 【luogu CF1553F】Pairwise Modulo(树状数组)(根号分治)
    PairwiseModulo题目链接:luoguCF1553F题目大意给你一个序列,对于每个前缀,要你求两两互相取模的结果的和。思路考虑新加入一个数增加的答案。那就是加两个部分:\(\sum......
  • Luogu P6880 [JOI 2020 Final] オリンピックバス 题解
    [P6880JOI2020Final]オリンピックバス-洛谷|计算机科学教育新生态(luogu.com.cn)两条前置知识:在图\(G\)上构造根为\(x\)的最短路树\(T\),我们删除任意一条......
  • AC自动机+DP luoguP4052 and P3311
    https://www.luogu.com.cn/problem/P4052题意:求长度为m的小写字母组成的字符串ss中包含给定字符串集合S中任意一个为子串的ss个数。思路:经典的在ac自动机上跑dp的套路,......
  • Luogu P3389【模板】高斯消元法
    题意给定一个线性方程组,对其求解。$1\leqn\leq100,\left|a_i\right|\leq{10}^4,\left|b\right|\leq{10}^4$题解因为之前贺题解的时候没有理解高斯-约......
  • LuoguP3377 【模板】左偏树(可并堆)
    题意如题,一开始有\(n\)个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:1xy:将第\(x\)个数和第\(y\)个数所在的小根堆合并(若第\(x\)或第\(y\)个......
  • luogu P6155 修改题解
    P6155题解闲话这是本蒟蒻写的第三篇题解了,前两篇都因为种种原因报废了,求管理过╥﹏╥正片大家好,我是一名STL选手,于是我用了大量STL+O2的代码过了这道题分析我的代码与......
  • 浅谈树链剖分
    树链剖分是把一棵树分割成若干条链,以便于维护信息的一种方法,其中最常用的是重链剖分(HeavyPathDecomposition,重路径分解),所以一般提到树链剖分或树剖都是指重链剖分。除此......
  • luogu P3571 [POI2014]SUP-Supercomputer
    题面传送门感觉考场上不一定做得出来的题目?首先我们可以得到每个点的深度,然后猜测这个只和每个层的深度有关。我们考虑这样一个贪心:对于每一层的每个点,如果这个点有子节......
  • luogu P3822 [NOI2017] 整数
    Link题解这里有一个很傻逼的无脑做法:https://www.luogu.com.cn/blog/80614/solution-p3822正常的正解做法是考虑用线段树维护每一位是什么,然后将\(a\)拆成二进制位,对......
  • luogu P3644 [APIO2015] 八邻旁之桥
    Link题解首先忽略掉同侧的询问。对于\(K=1\),它其实就是问一个点到其它点的距离之和最小值,直接找到中位数然后计算即可。对于一条路线,我们可以发现,如果建的桥里这两个......