[题解]HDU5756 Boss Bo

题目链接

#1.0 题意简述

给定一棵以 \(1\) 为根的 \(n\) 个点的树,\(q\) 次询问,每次给定一个集合 \(\{a_1,a_2,\dots,a_k\}\),定义“可行点”为不属于给定集合中的任意一点的子树的点,给定点 \(p\),有以下三种询问:

  • \(p\) 到所有可行点的距离之和;
  • \(p\) 到所有可行点的距离最小值;
  • \(p\) 到所有可行点的距离最大值;

定义树上每条边的长度为 \(1\),每个点到自己的距离为 \(0\),如果不存在可行点,则输出 -1

本题强制在线,记上一次的答案为 \(lastans\),每次询问给出 \(p'\),询问真正的 \(p\)

\[p=(p'+lastans)\bmod n+1. \]

若为第一个询问或上个询问答案为 \(-1\),则令 \(lastans=0.\)

多组数据,直至读入文件结束符(EOF)程序停止。

#2.0 大体思路

看到一个点被选中的影响是整棵子树,我们便考虑通过 dfn 序将树上的问题变成序列上的问题。如果我们当前序列保存的是 dfn 序为 \(i\) 的点与 \(p\) 的距离,那么问题变为了给定 \(k\) 个区间,询问在 \([1,n]\) 中去掉这 \(k\) 个区间后,剩下所有区间的权值和/最小值/最大值。

那么现在来考虑对于每个点如何维护距离序列。

考虑从一个父节点进入其的一个儿子,发现该儿子的子树中的点的距离全部减少 \(1\),其余的点的距离全部增加 \(1\)。因为整棵树以 \(1\) 为根,所以可以通过一次 dfs 得到 \(p=1\) 时的序列,之后的所有修改都可以从这个序列的基础上得到,不难想到此过程可以用可持久化线段树维护。

于是我们的具体实现流程为

  • 通过一次 dfs 得到 \(p=1\) 时的序列和其他必要信息;
  • 利用 dfs 得到的信息建出初始线段树,按 dfn 序通过区间修改得到各版本的序列;
  • 处理每个询问,得到在 \([1,n]\) 中去掉这 \(k\) 个区间后,剩下所有区间的答案;

对于每个询问,最多会要查询 \(k+1\) 个区间,于是时间复杂度为 \(O(\left(n+\sum k\right)\log n).\)

#3.0 一些细节

#3.1 如何得到询问区间

因为序列是按 dfn 序排列,所以不难得到这条性质:任意两个子树代表的区间要么为包含关系,要么毫不相关;

于是我们可以对选定点代表的区间按左端点进行排序,如果当前处理到的询问右端点 \(r\) 比上一个右端点 \(R\) 大,就询问 \([R+1,l-1]\) 这个区间,注意要特殊处理 \(R+1=l\) 的情况;最后需要判断是否要查询 \([R+1,n]\) 这个区间。

直接采用 sort() 不会影响时间复杂度。

#3.2 关于区间修改

因为涉及到区间修改,所以会涉及到区间标记,如果这里采用标记下传,下传标记时为保证不对历史版本的节点进行修改,便需要新建节点,空间复杂度大致约为 \(O(n\log^2n)\),而我们只有 \(128M\) 空间,无法接受,而区间加法的标记具有可加性,故应当采用标记永久化,这样空间复杂度便为 \(O(n\log n).\)

#4.0 Code

const int N = 200001;
const int M = 2000010;
const ll INF = 0x3fffffffffffffff;

template <typename T>
inline T Max(const T a, const T b) {return a > b ? a : b;}

template <typename T>
inline T Min(const T a, const T b) {return a < b ? a : b;}

struct Edge {int u, v, nxt;} e[N];
struct Node {
    int ls, rs; ll sum, mn, mx, lz;

    inline Node(int x) {sum = mn = mx = (ll)x; lz = ls = rs = 0;}
    inline Node() {ls = rs = sum = mn = mx = lz = 0;}

    inline void del() {ls = rs = sum = mn = mx = lz = 0;}
} p[M];

struct BAD_NODES {
    int l, r;
    inline bool operator < (const BAD_NODES b) const {return l < b.l;}
} bp[N << 1];

struct Answer {
    ll sum, mn, mx;

    inline Answer() {sum = 0, mn = INF, mx = -INF;}
    inline Answer(ll _sum, ll _mn, ll _mx) {
        sum = _sum, mn = _mn, mx = _mx;
    }

    inline Answer(Node P) {
        sum = P.sum, mn = P.mn, mx = P.mx;
    }
    
    inline void update(Answer b) {
        sum += b.sum, mn = Min(mn, b.mn), mx = Max(mx, b.mx);
    }

    inline Answer addtag(ll x, int l, int r) {
        sum += (r - l + 1) * x, mn += x, mx += x;
        return Answer(sum, mn, mx);
    }
};

int n, q, cnt, rt[N], head[N], ecnt = 1, f[N];
int T, dfn[N], d[N], lx[N], rx[N], id[N]; ll lsta;

inline void clear() {
    for (int i = 1; i <= cnt; ++ i) p[i].del();
    cnt = lsta = T = 0; d[0] = -1; ecnt = 1;
    mset(rt, 0); mset(head, 0);
}

inline int clone(int k) {p[++ cnt] = p[k]; return cnt;}

inline void add_edge(int u, int v) {
    e[ecnt].u = u, e[ecnt].v = v;
    e[ecnt].nxt = head[u], head[u] = ecnt ++;
}

inline void update(int k, int l, int r, ll x) {
    p[k].sum += (r - l + 1) * x;
    p[k].mn += x, p[k].mx += x, p[k].lz += x;
}

inline void pushup(int k, int l, int r) {
    int ls = p[k].ls, rs = p[k].rs;
    p[k].sum = p[ls].sum + p[rs].sum + (r - l + 1) * p[k].lz;
    p[k].mn = Min(p[ls].mn, p[rs].mn) + p[k].lz;
    p[k].mx = Max(p[ls].mx, p[rs].mx) + p[k].lz;
}

void build(int &k, int l, int r) {
    int mid = (l + r) >> 1; if (!k) k = ++ cnt;
    if (l == r) {p[k] = Node(d[id[l]]); return;}
    build(p[k].ls, l, mid);
    build(p[k].rs, mid + 1, r); pushup(k, l, r);
}

int modify(int k, int l, int r, int x, int y, ll c) {
    k = clone(k); int mid = (l + r) >> 1;
    if (x <= l && r <= y) {update(k, l, r, c); return k;}
    if (x <= mid) p[k].ls = modify(p[k].ls, l, mid, x, y, c);
    if (mid < y) p[k].rs = modify(p[k].rs, mid + 1, r, x, y, c);
    pushup(k, l, r);
    return k;
}

Answer query(int k, int l, int r, int x, int y, ll z) {
    if (x <= l && r <= y) return Answer(p[k]).addtag(z, l, r);
    int mid = (l + r) >> 1; Answer res;
    if (x <= mid) res.update(query(p[k].ls, l, mid, x, y, z + p[k].lz));
    if (mid < y) res.update(query(p[k].rs, mid + 1, r, x, y, z + p[k].lz));
    return res;
}

void get_dfn(int x, int fa) {
    dfn[x] = ++ T, lx[x] = T, id[T] = x;
    d[x] = d[fa] + 1, f[x] = fa;
    for (int i = head[x]; i; i = e[i].nxt)
      if (e[i].v != fa) get_dfn(e[i].v, x);
    rx[x] = T;
}

int main() {
    while (~scanf("%d%d", &n, &q)) {
        clear(); int pt = 0, k = 0, t = 0;
        for (int i = 1; i < n; ++ i) {
            int u, v; scanf("%d%d", &u, &v);
            add_edge(u, v); add_edge(v, u);
        }
        get_dfn(1, 0); build(rt[1], 1, n);
        for (int i = 2; i <= n; ++ i) {
            rt[i] = modify(rt[dfn[f[id[i]]]], 1, n, 1, n, 1);
            rt[i] = modify(rt[i], 1, n, lx[id[i]], rx[id[i]], -2);
        }
        while (q --) {
            scanf("%d%d%d", &k, &pt, &t);
            pt = (pt + lsta) % n + 1;
            int r = 0; Answer res;
            if (k) {
                for (int i = 1; i <= k; ++ i) {
                    int x; scanf("%d", &x);
                    bp[i] = (BAD_NODES){lx[x], rx[x]};
                } sort(bp + 1, bp + k + 1);
                if (bp[1].l == 1) {lsta = 0; printf("-1\n"); continue;}
                for (int i = 1; i <= k; ++ i) {
                    if (bp[i].r <= r) continue;
                    if (bp[i].l == r + 1) {r = bp[i].r; continue;}
                    res.update(query(rt[dfn[pt]], 1, n, r + 1, bp[i].l - 1, 0));
                    r = bp[i].r;
                }
            }
            if (r < n) res.update(query(rt[dfn[pt]], 1, n, r + 1, n, 0));
            if (t == 1) printf("%lld\n", lsta = res.sum);
            else if (t == 2) printf("%lld\n", lsta = res.mn);
            else printf("%lld\n", lsta = res.mx);
        }
    }
    return 0;
}
posted @ 2021-09-03 15:01  Dfkuaid  阅读(85)  评论(0编辑  收藏  举报