线段树专题二

Preblom One

699. 掉落的方块 - 力扣(LeetCode)

区间修改 +区间查询最值即可,需要离散化。

还有需要注意的地方就是一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。 为了处理这种信息,线段树维护的是每个格子,而不是数轴下标。

例如,[0, 1] 代表第一个格子,[1, 2] 代表第二个格子,以此类推 …

#define lc u << 1
#define rc u << 1 | 1

struct node{
    int l, r, mx, tag;
}tr[2100 * 4];

void pushup(int u){
    tr[u].mx = max(tr[lc].mx, tr[rc].mx);
}

void pushdown(int u){
    if(tr[u].tag != -1){
        tr[lc].mx = max(tr[lc].mx, tr[u].tag);
        tr[rc].mx = max(tr[rc].mx, tr[u].tag);
        tr[lc].tag = tr[rc].tag = tr[u].tag;
        tr[u].tag = -1;
    }
}

void build(int u, int l, int r){
    tr[u] = {l, r, 0, -1};
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(lc, l, mid);
    build(rc, mid + 1, r);
    pushup(u);
}

int ask(int u, int l, int r){
    if(l <= tr[u].l && r >= tr[u].r){
        return tr[u].mx;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    pushdown(u);
    int res = -1e9;
    if(l <= mid) res = max(res, ask(lc, l, r));
    if(r > mid) res = max(res, ask(rc, l, r));
    return res;
}

void modify(int u, int l, int r, int v){
    if(l <= tr[u].l && r >= tr[u].r){
        tr[u].mx = tr[u].tag = v;
        return ;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    pushdown(u); 
    if(l <= mid) modify(lc, l, r, v);
    if(r > mid) modify(rc, l, r, v);
    pushup(u);
}

class Solution {
public:
    vector<int> t, len; 
    vector<int> fallingSquares(vector<vector<int>>& positions) {
        for(auto &bk : positions){
            len.push_back(bk[1]); // 存储方块长度
            bk[1] += bk[0]; // 占据方格右边界
            bk[0] ++; // 左边为第 bk[0] 个方格
            t.push_back(bk[0]);
            t.push_back(bk[1]); 
        }
        sort(t.begin(), t.end());
        int sz = unique(t.begin(), t.end()) - t.begin();
        cout << sz << '\n';
        for(auto &bk : positions){ 
            // cout << bk[0] << ' ' << bk[1] << '\n';
            bk[0] = lower_bound(t.begin(), t.begin() + sz, bk[0]) - t.begin() + 1;
            bk[1] = lower_bound(t.begin(), t.begin() + sz, bk[1]) - t.begin() + 1;
        }
        build(1, 1, sz);
        for(auto bk : positions){
            cout << bk[0] << ' ' << bk[1] << '\n';
        }
        int cnt = 0;
        vector<int> res;
        for(auto &bk : positions){ 
            int t = ask(1, bk[0], bk[1]); 
            modify(1, bk[0], bk[1], t + len[cnt ++]);
            res.push_back(ask(1, 1, sz));  
            // for(int i = 1; i <= sz; i ++){
            //     cout << ask(1, i, i) << " \n"[i == sz];
            // }
        }
        return res;
    }
}; 

Problem Two

Problem - 4614

还是线段树的基础应用,区间查询 + 区间修改。

还要用一下二分,我二分了第一次出现花的位置,和出现 x x x 朵花的最靠左的位置。

 #include<bits/stdc++.h>
using namespace std;
#define int long long
#define lc u << 1
#define rc u << 1 | 1

int const N = 5e4 + 10;
int const inf = 9e18;

struct node{
	int l, r, sum;
	int tag;
}tr[N << 2];

int n, m, k, a, b;

void pushup(int u){
	tr[u].sum = tr[lc].sum + tr[rc].sum;
}

void pushdown(int u){
	if(tr[u].tag != inf){
		tr[lc].sum = (tr[lc].r - tr[lc].l + 1) * tr[u].tag;
		tr[rc].sum = (tr[rc].r - tr[rc].l + 1) * tr[u].tag;
		tr[lc].tag = tr[rc].tag = tr[u].tag;
		tr[u].tag = inf;
	}
}

void build(int u, int l, int r){
	tr[u] = {l, r, 0, inf};
	if(l == r) return;
	int mid = l + r >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
	pushup(u);
}

void change(int u, int l, int r, int v){
	if(l <= tr[u].l && r >= tr[u].r){
		tr[u].sum = v * (tr[u].r - tr[u].l + 1);
		tr[u].tag = v;
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	pushdown(u);
	if(l <= mid) change(lc, l, r, v);
	if(r > mid) change(rc, l, r, v);
	pushup(u);
}

int askSum(int u, int l, int r){
	if(l <= tr[u].l && r >= tr[u].r){
		return tr[u].sum;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	pushdown(u);
	int res = 0;
	if(l <= mid) res += askSum(lc, l, r);
	if(r > mid) res += askSum(rc, l, r);
	return res;
}

int askZeros(int l, int r){
	return (r - l + 1) - askSum(1, l, r);
}

void solve(){
	cin >> n >> m;
	build(1, 1, n);
	for(int i = 1; i <= m; i ++){
		cin >> k >> a >> b;
		if(k == 1){
			++ a;
			int t = askZeros(a, n);
			if(t == 0){
				cout << "Can not put any one.\n";
			}
			else{
				b = b > t ? t : b;
				int l = a, r = n;
				while(l < r){
					int mid = l + r >> 1;
					if(askZeros(a, mid) != 0) r = mid;
					else l = mid + 1;
				}
				cout << l - 1 << ' ';
				int t = l;
				l = a, r = n;
				while(l < r){
					int mid = l + r >> 1;
					if(askZeros(a, mid) >= b) r = mid;
					else l = mid + 1;
				}
				cout << l - 1 << '\n';
				change(1, t, l, 1);
			}
		}
		else{
			++ a, ++ b;
			int t = askSum(1, a, b);
			change(1, a, b, 0);
			cout << t << '\n';
		}
		// for(int i = 1; i <= n; i ++){
		// 	cout << askSum(1, i, i) << " \n"[i == n];
		// }
	}
	cout << '\n';
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cout << "[pre]";

	int T = 1;
	cin >> T;
	while (T --){
		solve();
	}

	cout << "[/pre]";

	return 0;
}


Problem Three

区间开平方 + 查询区间总和

x = 1 0 12 x=10^{12} x=1012 开根六次就会变成 1 1 1

所以我们维护每条线段的最值,进行区间开根时,如果一条线段 max ⁡ = 1 \max = 1 max=1 ,那么我们遇到它时就不在对它进行开根。

由于每个叶子结点最多开根 6 6 6 次,树的高度为 log ⁡ n \log n logn ,最多 n n n 个叶子结点,所以时间复杂度是 6 n log ⁡ n 6n \log n 6nlogn

#define lc u << 1
#define rc u << 1 | 1

struct node{
	int l, r, sum, mx;
}tr[N << 2];

void pushup(int u){
	tr[u].sum = tr[lc].sum + tr[rc].sum;
	tr[u].mx = max(tr[lc].mx, tr[rc].mx);
}

void build(int u, int l, int r){
	tr[u] = {l, r, a[l], a[l]};
	if(l == r) return ;
	int mid = l + r >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
	pushup(u);
}

// 区间开根
void segSqrt(int u, int l, int r){
	if(tr[u].mx == 1) return ; // 优化
	if(tr[u].l == tr[u].r){
		int t = sqrt(tr[u].sum);
		tr[u].sum = tr[u].mx = t;
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) segSqrt(lc, l, r);
	if(r > mid) segSqrt(rc, l, r);
	pushup(u); 
}

// 查询区间和
int askSum(int u, int l, int r){
	if(l <= tr[u].l && r >= tr[u].r){
		return tr[u].sum;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	int res = 0;
	if(l <= mid) res += askSum(lc, l, r);
	if(r > mid) res += askSum(rc, l, r);
	return res;
}

Problem Four

同第三题, x x x 取模之后要么不变,要么 ≤ x 2 \leq \frac x 2 2x

所以对于一个数 v v v log ⁡ v \log v logv 次就会变为 1 1 1

关于下降一半多,以 x    m o d    y x \; mod \; y xmody 为例,如果 y ≤ x 2 y \leq \frac x 2 y2x ,那么一定满足。

如果 y > x 2 y > \frac x 2 y>2x , x    m o d    y = x − ⌊ x y ⌋ y = x − y < x 2 x \; mod \;y = x - \lfloor \frac x y \rfloor y = x - y <\frac x 2 xmody=xyxy=xy<2x

期望复杂度为 : O ( n log ⁡ n log ⁡ v ) O(n\log n\log v) O(nlognlogv)

#define lc u << 1
#define rc u << 1 | 1

struct node{
	int l, r, sum, mx;
}tr[N << 2];

void pushup(int u){
	tr[u].sum = tr[lc].sum + tr[rc].sum;
	tr[u].mx = max(tr[lc].mx, tr[rc].mx);
}

void build(int u, int l, int r){
	tr[u] = {l, r, a[l], a[l]};
	if(l == r) return ;
	int mid = l + r >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
	pushup(u);
}

// 区间对 v 取模
void segMod(int u, int l, int r, int v){
	if(v > tr[u].mx) return ;
	if(tr[u].l == tr[u].r){
		tr[u].sum %= v;
		tr[u].mx = tr[u].sum;
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) segMod(lc, l, r, v);
	if(r > mid) segMod(rc, l, r, v);
	pushup(u);
}

// 修改 a[p] = x;
void modify(int u, int p, int x){
	if(tr[u].l == tr[u].r){
		tr[u].sum = tr[u].mx = x;
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	if(p <= mid) modify(lc, p, x);
	else modify(rc, p, x);
	pushup(u);
}

// 查询 summary[l ~ r]
int askSum(int u, int l, int r){
	if(l <= tr[u].l && r >= tr[u].r){
		return tr[u].sum;
	}
	int res = 0;
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) res += askSum(lc, l, r);
	if(r > mid) res += askSum(rc, l, r);
	return res;
}

Problem Five

很有意思的一道题目,离散化 + 线段树即可。

特别注意下面这种样例,

10000 5

1 5

1 3

4 5

能看到三张海报,直接离散化却是两张,发现离散化的过程中丢失了信息。

所以将所有点加入到数组 t t t 排序准备离散化时,对于每个 t i < t i + 1 t_i<t_{i+1} ti<ti+1 ,在他们之间添加一个 ∈ [ t i + 1 , t i + 1 − 1 ] \in [t_i+1,t_{i+1}-1] [ti+1,ti+11] 的点。

#include<bits/stdc++.h>
using namespace std;
#define int long long

#define lc u << 1
#define rc u << 1 | 1

int const N = 3100;
int const inf = 2e18;

struct node{
	int l, r, sum, tag;
}tr[N << 2];

int n, a[N], b[N];
int cnt = 0, t[N];

void pushup(int u){
	tr[u].sum = tr[lc].sum + tr[rc].sum;
}

void pushdown(int u){
	if(tr[u].tag != inf){
		tr[lc].sum = (tr[lc].r - tr[lc].l + 1) * tr[u].tag;
		tr[rc].sum = (tr[rc].r - tr[rc].l + 1) * tr[u].tag;
		tr[lc].tag = tr[rc].tag = tr[u].tag;
		tr[u].tag = inf;
	}
}

void build(int u, int l, int r){
	tr[u] = {l, r, inf, inf};
	if(l == r) return ;
	int mid = l + r >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
	pushup(u);
}

void change(int u, int l, int r, int v){
	if(l <= tr[u].l && r >= tr[u].r){
		tr[u].sum = v * (tr[u].r - tr[u].l + 1);
		tr[u].tag = v;
		return ;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	pushdown(u);
	if(l <= mid) change(lc, l, r, v);
	if(r > mid) change(rc, l, r, v);
	pushup(u);
}

int ask(int u, int l, int r){
	if(l <= tr[u].l && r >= tr[u].r){
		return tr[u].sum;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	pushdown(u);
	int res = 0;
	if(l <= mid) res += ask(lc, l, r);
	if(r > mid) res += ask(rc, l, r);
	return res;
}

 // 离散化少东西了
void solve(){
	cin >> n >> n;
	for(int i = 1; i <= n; i ++){
		cin >> a[i] >> b[i];
		t[++ cnt] = a[i], t[++ cnt] = b[i]; 
    }
    sort(t + 1, t + cnt + 1);
    vector<int> midPoint;
    for(int i = 1; i + 1 <= cnt; i ++){
    	int l = t[i], r = t[i + 1];
    	if(r - l > 1){
    		midPoint.push_back(l + 1);
    	}
    }
    for(auto &x : midPoint) t[++ cnt] = x;
    sort(t + 1, t + cnt + 1);
    int sz = unique(t + 1, t + cnt + 1) - t - 1;
    for(int i = 1; i <= n; i ++){
    	a[i] = lower_bound(t + 1, t + sz + 1, a[i]) - t;
    	b[i] = lower_bound(t + 1, t + sz + 1, b[i]) - t;
    	// cout << a[i] << ' ' << b[i] << '\n';
    }
    build(1, 1, sz);
    for(int i = 1; i <= n; i ++){
    	change(1, a[i], b[i], i);
    }
    set<int> res;
    for(int i = 1; i <= sz; i ++){
    	int t = ask(1, i, i);
    	if(t != inf) res.insert(t);
    }
    cout << res.size() << '\n';
}
https://www.luogu.com.cn/record/178161094
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int T = 1;
	while (T --){
		solve();
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值