bzoj4129 Haruna’s Breakfast 树上带修莫队+分块

题目传送门

https://lydsy.com/JudgeOnline/problem.php?id=4129

题解

考虑没有修改的序列上的版本应该怎么做:

弱化的题目应该是这样的:

给定一个序列,每次询问区间 \([l, r]\) 中元素的最小没有出现的自然数。



这个弱化的版本可以用离线+线段树二分水掉。但是这个做法显然不太好搬到树上做。

上面的弱化版还有一个莫队做法:可以用莫队维护出来每一个区间的每一个数的出现为次数。把出现过的数通过分块表示出来,于是查询的时候枚举每一个块,寻找第一个不满的块。



那么放到树上以后,就用树上莫队吧。

既然还有修改,那么就是树上带修莫队。



注意一下:

因为数据范围比较大,所以需要离散化。但是离散化的时候为了防止漏掉一些数(某两个数在数轴上是不相邻的,但是在离散化以后相邻),需要把出现过的每一个数 \(+1\) 放进被离散化的序列中。



UPD:

貌似不需要离散化,答案一定 \(< n\),所以只需要保留 \(<n\) 的数就可以了。

我真的有点智商低下。

一定要考虑题目中的一些量是否有用,是否可以简化。



时间复杂度 \(O(m\cdot n^{\frac 23})\)。

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back

template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}

typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;

template<typename I> inline void read(I &x) {
    int f = 0, c;
    while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
    x = c & 15;
    while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
    f ? x = -x : 0;
}

const int N = 5e4 + 7;
const int B = 200 + 7;

#define bl(x) (((x) - 1) / blo + 1)
#define st(x) (((x) - 1) * blo + 1)
#define ed(x) std::min((x) * blo, dis)

int n, m, dis, dfc, dfc2, m1, m2, blo, now;
int a[N], b[N << 2], ans[N];
int dep[N], f[N], siz[N], son[N], dfn[N], pre[N], top[N];
int ldfn[N], rdfn[N], seq[N << 1];
int used[N], s[N << 2], ss[B], cnt[N << 2];

struct Update { int id, x, k, pre; } u[N];
struct Query {
    int id, l, r, lca, *ans;
    inline bool operator < (const Query &b) const {
        if (bl(l) != bl(b.l)) return l < b.l;
        if (bl(r) != bl(b.r)) return r < b.r;
        return id < b.id;
    }
} q[N];

struct Edge { int to, ne; } g[N << 1]; int head[N], tot;
inline void addedge(int x, int y) { g[++tot].to = y, g[tot].ne = head[x], head[x] = tot; }
inline void adde(int x, int y) { addedge(x, y), addedge(y, x); }

inline void dfs1(int x, int fa = 0) {
    dep[x] = dep[fa] + 1, f[x] = fa, siz[x] = 1;
    for fec(i, x, y) if (y != fa) dfs1(y, x), siz[x] += siz[y], siz[y] > siz[son[x]] && (son[x] = y);
}
inline void dfs2(int x, int pa) {
    top[x] = pa, dfn[x] = ++dfc, pre[dfc] = x;
    ldfn[x] = ++dfc2, seq[dfc2] = x;
    if (!son[x]) return (void)(rdfn[x] = ++dfc2, seq[dfc2] = x); dfs2(son[x], pa);
    for fec(i, x, y) if (y != f[x] && y != son[x]) dfs2(y, y);
    rdfn[x] = ++dfc2, seq[dfc2] = x;
}
inline int lca(int x, int y) {
    while (top[x] != top[y]) dep[top[x]] > dep[top[y]] ? x = f[top[x]] : y = f[top[y]];
    return dep[x] < dep[y] ? x : y;
}

inline void qadd(int x, int k) {
    s[x] += k, ss[bl(x)] += k;
    assert(s[x] == 0 || s[x] == 1);
    assert(ss[bl(x)] <= ed(bl(x)) - st(bl(x)) + 1 && ss[bl(x)] >= 0);
}
inline int qans() {
    for (int i = 1; i <= bl(dis); ++i) if (ss[i] != ed(i) - st(i) + 1)
        for (int j = st(i); j <= ed(i); ++j) if (!s[j]) return b[j - 1] + 1;
    return b[dis] + 1;
}

inline void madd(int x) {
    ++cnt[x];
    if (cnt[x] == 1) qadd(x, 1);
}
inline void mdel(int x) {
    --cnt[x];
    if (cnt[x] == 0) qadd(x, -1);
}
inline void mupd(int x) {
    if (used[x]) mdel(a[x]);
    else madd(a[x]);
    used[x] ^= 1;
}

inline void cadd(int i) {
    int x = u[i].x, k = u[i].k;
    if (used[x]) mdel(a[x]);
    u[i].pre = a[x], a[x] = k;
    if (used[x]) madd(a[x]);
}
inline void cdel(int i) {
    int x = u[i].x;
    if (used[x]) mdel(a[x]);
    assert(a[x] == u[i].k);
    a[x] = u[i].pre;
    if (used[x]) madd(a[x]);
}
inline void cupd(int i) {
    while (now < m1 && u[now + 1].id <= i) cadd(++now);
    while (now && u[now].id > i) cdel(now--);
}

inline void lsh() {
    for (int i = 1; i <= dis; ++i) b[i + dis] = b[i] + 1;
    dis *= 2;
    std::sort(b + 1, b + dis + 1);
    dis = std::unique(b + 1, b + dis + 1) - b - 1;
    for (int i = 1; i <= n; ++i) a[i] = std::lower_bound(b + 1, b + dis + 1, a[i]) - b;
    for (int i = 1; i <= m1; ++i) u[i].k = std::lower_bound(b + 1, b + dis + 1, u[i].k) - b;
    b[0] = -1;
}

inline void work() {
    lsh();
    blo = pow(n, 2.0 / 3);
    std::sort(q + 1, q + m2 + 1);
    int l = 1, r = 0;
    for (int i = 1; i <= m2; ++i) {
        cupd(q[i].id);
        while (l > q[i].l) mupd(seq[--l]);
        while (r < q[i].r) mupd(seq[++r]);
        while (l < q[i].l) mupd(seq[l++]);
        while (r > q[i].r) mupd(seq[r--]);
        if (q[i].lca) assert(!used[q[i].lca]);
        if (q[i].lca) mupd(q[i].lca);
        *q[i].ans = qans();
        if (q[i].lca) mupd(q[i].lca);
    }
    for (int i = 1; i <= m2; ++i) printf("%d\n", ans[i]);
}

inline void init() {
    read(n), read(m);
    for (int i = 1; i <= n; ++i) read(a[i]), b[++dis] = a[i];
    int x, y;
    for (int i = 1; i < n; ++i) read(x), read(y), adde(x, y);
    dfs1(1), dfs2(1, 1);
    for (int i = 1; i <= m; ++i) {
        int opt;
        read(opt);
        if (opt == 0) ++m1, read(u[m1].x), read(u[m1].k), u[m1].id = i, b[++dis] = u[m1].k;
        else {
            int x, y, p;
            read(x), read(y);
            if (ldfn[x] > ldfn[y]) std::swap(x, y);
            p = lca(x, y), ++m2;
            if (p == x) q[m2] = (Query){ i, ldfn[x], ldfn[y], 0, ans + m2 };
            else q[m2] = (Query){ i, rdfn[x], ldfn[y], p, ans + m2 };
        }
    }
}

int main() {
#ifdef hzhkk
    freopen("hkk.in", "r", stdin);
#endif
    init();
    work();
    fclose(stdin), fclose(stdout);
    return 0;
}

原文地址:https://www.cnblogs.com/hankeke/p/bzoj4129.html

时间: 2024-08-05 20:30:46

bzoj4129 Haruna’s Breakfast 树上带修莫队+分块的相关文章

【BZOJ-3052】糖果公园 树上带修莫队算法

3052: [wc2013]糖果公园 Time Limit: 200 Sec  Memory Limit: 512 MBSubmit: 883  Solved: 419[Submit][Status][Discuss] Description Input Output Sample Input Sample Output 84 131 27 84 HINT Source Solution 树上带修莫队 本质还是树上莫队,详情可以转 BZOJ-3757苹果树 但是这里需要修改,就需要一些特殊的地方

BZOJ4129(树上带修莫队)

树上莫队的基本思路是把树按dfs序分块,然后先按x所在块从小到大排序,再按y所在块从小到大排序,处理询问即可. 这道题带修改,再加一个时间维即可. 时间复杂度据说是$n^{\frac53}$,不知道是为什么. (块大小改成3也过了什么鬼..) #include <cstdio> #include <algorithm> using namespace std; const int N=50005,M=100005,B=1357,T=223; int n,m,e,x,y,t,tp,o

【Luogu P4074】[WC2013]糖果公园(树上带修改莫队)

题目描述 Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园游玩. 糖果公园的结构十分奇特,它由 \(n\) 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 \(1\) 至 \(n\).有 \(n-1\) 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有其它游览点. 糖果公园所发放的糖果种类非常丰富,总共有 \(m\) 种,

[BZOJ 4129]Haruna’s Breakfast(树上带修改莫队)

Description Haruna每天都会给提督做早餐! 这天她发现早饭的食材被调皮的 Shimakaze放到了一棵 树上,每个结点都有一样食材,Shimakaze要考验一下她. 每个食材都有一个美味度,Shimakaze会进行两种操作: 1.修改某个结点的食材的美味度. 2.对于某条链,询问这条链的美味度集合中,最小的未出现的自然数是多少.即mex值. 请你帮帮Haruna吧. Solution 树上带修改莫队 统计答案的时候也分块查询,找到第一个没满的块开始一个一个找 #include<i

[UOJ #58][WC2013]糖果公园(树上带修改莫队)

Description Solution 树上带修改莫队…!VFK的题解写得很清楚啦 (我的程序为什么跑得这么慢…交的时候总有一种自己在卡测评的感觉…) #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define MAXN 100005 typedef long l

【带修莫队】【权值分块】bzoj3196 Tyvj 1730 二逼平衡树

这题用了三种算法写: 分块+二分:O(n*sqrt(n*log(n)) 函数式权值分块:O(n*sqrt(n)) 带修莫队+权值分块:O(n5/3) 结果……复杂度越高的实际上跑得越快……最后这个竟然进第一页了…… #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int f,C; inline void R(int &

2120: 数颜色(带修莫队)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2120 2120: 数颜色 Time Limit: 6 Sec  Memory Limit: 259 MBSubmit: 10514  Solved: 4398[Submit][Status][Discuss] Description 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R

CF940F Machine Learning(带修莫队)

首先显然应该把数组离散化,然后发现是个带修莫队裸题,但是求mex比较讨厌,怎么办?其实可以这样求:记录每个数出现的次数,以及出现次数的出现次数.至于求mex,直接暴力扫最小的出现次数的出现次数为0的正整数,就一句话,这样看似会超时,实际上是O(√n)的复杂度.为什么?假设存在出现1,2,...,x的出现次数,则Σi(1<=i<=x)<=n,即x*(x+1)<=2*n,所以x至多是√n级别.很多人再把出现次数分块,根本没必要.然后考虑把数组分块的块大小,每次移动左指针,为O(n*块大

莫队 + 带修莫队

莫队其实就是一个带优化的暴力,通过将区间询问按一定规则进行排序,从而优化过程,求出答案. 举一例子:(例子不具备权威性,只是让读者了解莫队是干啥的) /* 输入四个区间 1 4 初始条件,L= R = 0, 将R遍历到4 需要走4步 L走一步,共5次 4 8 继承上一次 L 和 R 的值,L从1到4 需要3次,R从4到8,需4次, 总计8次 2 9 同理继承, L回退2次, R前进一次 总计3次 1 2 同理,L回退1次,R回退7次 总计8次 如果直接暴力,计算机将要计算 5+8+3+8=24次