线段树相关

Hotel

大意:给定\(n\)个旅馆,初始全为空。有两种询问方式。
1.输入\(x\),查找满足连续空旅馆数\(\ge\)x的最小编号,并在此x间开房。
2.输入\(l,r\), 给指定区间退房。

思路


对线段树的节点维护以下信息:

  • 该区间的长度\(len\)
  • 该区间从左端点开始的最长连续空旅馆长度\(lnum\)
  • 该区间从右端点开始的最长连续空旅馆长度\(rnum\)
  • 该区间最长的连续空旅馆长度\(num\)
  • 懒标记\(lz\) \(\rightarrow\left\{\begin{array}{l}1&覆盖\\2&清空\end{array}\right.\)

修改一段区间时,由于线段树的性质,会将区间分解为若干结点所代表的区间,在覆盖或清空时,可直接将\(lz\)设为\(1/2\),同时将\(lnum,rnum.num\)设为\(0/len\)。具体合并下传等操作见代码。
实现时有一些细节注意一下,要想清楚线段树各项操作的过程,注意当\(n=1\)和对叶子节点的处理。


#include "bits/stdc++.h"
using namespace std;
class segtree {
public:
    struct node {
        int lz = 0;
        int num = 0;
        int lnum = 0;
        int rnum = 0;
        int len;
        void apply(int l, int r, int v) {
            if(v == 1) {
                lz = 1;
                num = lnum = rnum = 0;
            }
            else if(v == 2) {
                lz = 2;
                num = lnum = rnum = r - l + 1;
            }
            else {
                num = lnum = rnum = len = r - l + 1;
            }
        }
    };
    node unite(const node& a, const node& b) const {
         node res;
         res.len = a.len + b.len;
         if(b.rnum == b.len) {
             res.rnum = b.rnum + a.rnum;
         }
         else {
             res.rnum = b.rnum;
         }
         if(a.lnum == a.len) {
             res.lnum = a.lnum + b.lnum;
         }
         else {
             res.lnum = a.lnum;
         }
         res.num = max(max(res.lnum, res.rnum), a.rnum + b.lnum);
         res.num = max(res.num, max(a.num, b.num));
         return res;
    }
    inline void push(int x, int l, int r) {
        int y = (l + r) >> 1;
        int z = x + ((y - l + 1) << 1);
        if (tree[x].lz != 0) {
            tree[x + 1].apply(l, y, tree[x].lz);
            tree[z].apply(y + 1, r, tree[x].lz);
            tree[x].lz = 0;
        }
    }
    inline void pulL(int x, int z) {
        tree[x] = unite(tree[x + 1], tree[z]);
}
int n;
vector<node> tree;
void build(int x, int l, int r) {
    if (l == r) {
        tree[x].apply(l, r, 0);
        return;
    }
    int y = (l + r) >> 1;
    int z = x + ((y - l + 1) << 1);
    build(x + 1, l, y);
    build(z, y + 1, r);
    pulL(x, z);
}
int get(int x, int l, int r, int p) {
    int res = -1;
    if (l == r) {
        if(tree[x].num >= p) return l;
        else return -1;
    }
    int y = (l + r) >> 1;
    int z = x + ((y - l + 1) << 1);
    push(x, l, r);
    
    if(tree[x + 1].num >= p) {
        res = get(x + 1, l, y, p);
    }
    else if(tree[x + 1].rnum + tree[z].lnum >= p) {
        res = y - tree[x + 1].rnum + 1;
    }
    else if(tree[z].num >= p) {
        res = get(z, y + 1, r, p);
    }
    pulL(x, z);
    return res;
}
template <typename... M>
void modify(int x, int l, int r, int lL, int rr, const M&... v) {
    if (lL <= l && r <= rr) {
        tree[x].apply(l, r, v...);
        return;
    }
    int y = (l + r) >> 1;
    int z = x + ((y - l + 1) << 1);
    
    push(x, l, r);
    if (lL <= y) {
        modify(x + 1, l, y, lL, rr, v...);
    }
    if (rr > y) {
        modify(z, y + 1, r, lL, rr, v...);
    }
    pulL(x, z);
}
segtree(int _n) : n(_n) {
    assert(n > 0);
    tree.resize(2 * n - 1);
    build(0, 0, n - 1);
}
int get(int p) {
    return get(0, 0, n - 1, p);
}
template <typename... M>
void modify(int lL, int rr, const M&... v) {
    assert(0 <= lL && lL <= rr && rr <= n - 1);
    modify(0, 0, n - 1, lL, rr, v...);
}
};

void solve() {               
    int n, m;
    cin >> n >> m;
    segtree st(n);
    while(m--) {
        int op;
        cin >> op;
        if(op == 1) {
            int x;
            cin >> x;
            int res = st.get(x);
            if(res == -1) {
                cout << 0 << '\n';
            }
            else {
                cout << res + 1 << '\n';
                st.modify(res, res + x - 1, 1);
            }
        }
        else {
            int x, y;
            cin >> x >> y;
            --x;
            st.modify(x, x + y - 1, 2);
        }
    }
}

似乎维护最大子段和一类的都是这个套路,某最大的某连续区间满足...要么在左区间,要么在右区间,要么就在两区间合并处。

跳树

大意: 一个兔子在一颗完全二叉树上移动,每次移动可以移动到两个儿子节点或父亲节点,给定一串移动序列,每次询问给定起点,问从起点经过\(l-r\)这段移动,终点的位置。需要支持单点修改

思路


一条路径可以用一个二进制数来表示,从左到右,若是1就向右儿子移动,0则向左儿子移动。在路径的二进制表达中,不体现退格。对于一段\(l-r\)的操作,我们可以将其看作一个整体,当执行这段操作序列时,需要先退格多少,再怎样进行移动。可以发现若当前在s点,执行过后的终点为 \(((s >> backnum) << lenofpath) + path\)。注意到这个几个信息线段树可以维护。

合并代码

node unite(const node& a, const node& b) const {
        node res;
        //xy意思是需要先向祖先跳几步
        if(b.xy < a.len) {
            res.path = ((a.path >> b.xy) << b.len) + b.path;
            res.xy = a.xy;
            res.len = a.len - b.xy + b.len;
        }
        else {
            res.xy = a.xy + b.xy - a.len;
            res.path = b.path;
            res.len = b.len;
        }
        return res;
    }

修改是单点修改,所以不需要维护懒标记。

[SDOI2009] HH的项链

大意: 给出n个数,q次询问,每次询问要求输出\([l,r]\)有多少个不同的数

思路


先将询问离线,按右端点排序。(套路

开一个线段树或树状数组维护一段区间中真正有效的点的个数。同样的数取最后出现的为有效的数,枚举右端点时,每次将上一个位置的贡献取消,这个位置的贡献加一。然后对同一个\(r,\)输出所有\(sum(l,r)\)

关键代码

    for(int i = 1; i <= n; i++) {
        if(last[a[i]]) {
            f.add(last[a[i]], -1);
            f.add(i, 1);
            last[a[i]] = i;
        }
        else {
            f.add(i, 1);
            last[a[i]] = i;
        }
        while(d[now].r == i) {
            ans[d[now].id] = f.sum(d[now].l, d[now].r);
            now++;
        }
    }

Picture

大意:求\(n\)个矩形并起来的外围周长

思路


利用扫描线算法,从下往上扫,可以求出当前图上在这一高度上有多少独立的线段,这些线段的两侧在扫到下一条线时画出的长度就是有效的竖直长度。同理在从左往右扫一遍,把两个结果加起来就是答案。如何求有多少独立的线段呢?可以用线段树维护。

  • 该段区间被完整覆盖了\(lz\)
  • 该段区间的独立不相交线段数\(num\)
  • 左,右端点是否被覆盖\(lv,rv\)

细节有很多:

\(1.\)区间\([l,r]\)是一段实数区间,在树上使用\([l,r - 1]\)表示。

\(2.\) 对于\(lz\)这个值可以理解为独立的,父节点被覆盖与子节点无关。

\(3.\) 为什么可以不用下传,一是因为查询只查根,二是因为,在修改时,如果是新加一条线段,其子区间的信息不需更新,因为用不到,当别的区间需要访问时,两区间必然相交,最终根节点上的\(num\)结果一样;如果是减掉一条线段,这时这个线段上的区间的全覆盖状态可能取消,这时其\(num\)值需要通过子区间来更新,如果当初下传了的话还得把所有子区间的状态全更新一遍才能确定。

\(4.\) 任何区间修改完都要马上\(pushup\)

\(5.\) 叶子节点不能合并子节点,单独处理

pushup代码

void pushup(int o,int l,int r)
{
	if(tree[o].sum)//此区间之前被一整个线段覆盖过
	{
		tree[o].num=1;
		tree[o].len=r-l+1;
		tree[o].lflag=tree[o].rflag=1;
	}
	else if(l==r)//这是一个叶节点
	{
		tree[o].num=0;
		tree[o].len=0;
		tree[o].lflag=tree[o].rflag=0;
	}
	else//一般情况
	{
		tree[o].num=tree[lson].num+tree[rson].num;
		if(tree[lson].rflag&&tree[rson].lflag)tree[o].num--;//flag的用处
		tree[o].len=tree[lson].len+tree[rson].len;
		tree[o].lflag=tree[lson].lflag;
		tree[o].rflag=tree[rson].rflag;
		//注意:sum不会被修改,只有当它被一整个线段覆盖时才会修改
	}
}
posted @ 2022-05-02 22:58  Epochephemeral  阅读(49)  评论(0)    收藏  举报