8.13——962G(线段树查询区间min数量trick)
先讲重点:如何用线段树维护区间最小值以及数量?直接按照\(\{val,cnt\}\) 的方式将二者绑定在一起,并稍微改动下 \(pushup\) 和 \(query\) 函数即可。具体细节见如下代码:
struct Min{
int val, cnt;
};
struct Node{
int l, r;
Min o; // {val, cnt}: {最小值,最小值出现次数}
int tag;
}tr[N << 2];
struct SegTree{
#define lc p<<1
#define rc p<<1|1
void pushup(int p){
if(tr[lc].o.val > tr[rc].o.val){
tr[p].o = tr[rc].o;
}
else if(tr[lc].o.val < tr[rc].o.val){
tr[p].o = tr[lc].o;
}
else{
tr[p].o = {tr[lc].o.val, tr[lc].o.cnt + tr[rc].o.cnt};
}
}
void pushdown(int p){
if(tr[p].tag){
tr[lc].tag += tr[p].tag;
tr[rc].tag += tr[p].tag;
tr[lc].o.val += tr[p].tag;
tr[rc].o.val += tr[p].tag;
tr[p].tag = 0;
}
}
void build(int p, int l, int r){
tr[p] = {l, r, Min{0, 1}, 0};
if(l == r) return;
int mid = l + r >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(p);
}
void update(int p, int l, int r, int v){
if(l <= tr[p].l && tr[p].r <= r){
tr[p].o.val += v;
tr[p].tag += v;
return;
}
pushdown(p);
int mid = tr[p].l + tr[p].r >> 1;
if(l <= mid) update(lc, l, r, v);
if(r > mid) update(rc, l, r, v);
pushup(p);
}
Min query(int p, int l, int r){
if(l <= tr[p].l && tr[p].r <= r){
return tr[p].o;
}
pushdown(p);
int mid = tr[p].l + tr[p].r >> 1;
if(r <= mid) return query(lc, l, r);
if(l > mid) return query(rc, l, r);
Min lson = query(lc, l, r), rson = query(rc, l, r);
Min t;
if(lson.val < rson.val){
t = lson;
}
else if(lson.val > rson.val){
t = rson;
}
else{
t = {lson.val, lson.cnt + rson.cnt};
}
return t;
}
}seg;
G
容易想到破环为链。这样做相当于是固定删除了某一条边,好处在于:所有关系可以仅表示成一个区间的形式(若不破环则每种关系包括两个区间)。于是可以利用滑窗+线段树维护所有长度为 \(n\) 的子区间的覆盖情况:某条边只要未被任意一个关系表示的线段覆盖,这条边就可以被删除。于是问题转化成了查询区间内 \(0\) 的数量。由于 \(0\) 一定是区间最小值,因此等价于查询区间内最小值的数量,直接用上述板子即可。具体细节见代码。