Vases and Flowers(线段树)

题目链接:Vases and Flowers

大致题意

给定一个整数n, 表示有n个花瓶(初始为空花瓶), 编号从0~n-1. 有如下两种操作:

①从编号为x的花瓶开始, 要放y朵花, 从前往后一次遍历, 如果是空花瓶则放一朵花在里面, 直至放完所有花或者遍历到最后一个花瓶为止. 倘若此时还有花放不下, 则将它们直接丢弃.
②清理[l, r]区间的所有花瓶, 如果里面有花则将其丢弃

对于每个操作①, 需要输出第一个放花的位置和最后一个放花的位置. 倘若一朵花都放不下, 需要输出"Can not put any one."
对于每个操作②, 需要输出该区间被清理的花的数量

解题思路

线段树的区间修改和区间查询.

线段树维护当前区间内可以放花朵的数目.

解法一:

对于每个操作①而言, 首先用全局变量L, R维护第一个放花与最后一个放花的位置.
找到符合要求的区间后, 如果当前的区间可以放的花朵数量<=需要放置, 则将当前区间放满花朵, 同时查看当前区间第一个和最后一个空花瓶位置, 并更新L与R. 反之若当前区间不满足要求应当先递归左子树, 再递归右子树(因为要尽可能把前面的空花瓶都放满).
上述思路我们需要实现两个操作: 找到当前区间第一个空花瓶与最后一个空花瓶. 我们可以分别写两个函数来实现.

对于每个操作②而言, 就是简单的区间查询, 我们可以顺便进行花瓶清空操作.

解法二:

首先说操作②的实现: 我们的查询函数会返回[l, r]区间的空花瓶数目, 则操作②清理花的数目= [l, r]长度 - [l, r]空花瓶个数.

对于操作①: 我们其实可以通过查询[x, n]区间空花瓶的个数, 来判断是否一朵花都放不下. 如果可以放的下花的话, 我们可以采用二分的思路去二分[x, n]区间, 来得到第num个空花瓶所在的位置.

特别注意: 对于最后放花的位置: 如果可以放得下所有y朵花, 我们应查询第y个空花瓶的位置, 反之我们应当查询[x, n]区间最后一个空花瓶所在的位置.

AC代码

/* 解法一: 全局变量 */
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E4 + 10;
struct node {
    int l, r;
    int cou; //代表可以放花的数目
    int lazy; //由于lazy = 0为一种修改标签, 所以本题lazy初值赋为-1
}t[N << 2];
void pushdown(node& op, int lazy) {
    op.cou = lazy * (op.r - op.l + 1);
    op.lazy = lazy;
}
void pushdown(int x) {
    if (t[x].lazy == -1) return;
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = -1;
}

void pushup(int x) { t[x].cou = t[x << 1].cou + t[x << 1 | 1].cou; }

void build(int l, int r, int x = 1) {
    t[x] = { l, r, 1, -1 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    pushup(x);
}

int L, R;
int findleft(int x) { //找到当前最左侧可以放花的位置
    if (t[x].l == t[x].r) return t[x].l;
    pushdown(x);
    return t[x << 1].cou ? findleft(x << 1) : findleft(x << 1 | 1); //左侧可以放花, 则递归左子树, 反之则递归右子树
}
int findright(int x) { //找到当前最右侧可以放花的位置
    if (t[x].l == t[x].r) return t[x].l;
    pushdown(x);
    return t[x << 1 | 1].cou ? findright(x << 1 | 1) : findright(x << 1);
}

void modify(int l, int r, int& c, int x = 1) { //c记得传递引用类型, 代表要放花的数目
    if (!c || !t[x].cou) return;
    if (l <= t[x].l && r >= t[x].r) {
        if (t[x].cou <= c) {
            L = min(L, findleft(x)), R = max(R, findright(x)); //用全局变量L, R维护答案
            c -= t[x].cou, pushdown(t[x], 0);
            return;
        }
        if (t[x].l == t[x].r) return;
    }
    
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(x);
}

int ask(int l, int r, int x = 1) {
    if (l <= t[x].l && r >= t[x].r) {
        int res = t[x].r - t[x].l + 1 - t[x].cou; //区间长度 - 可以放花的数目 = 已经放花的数目
        pushdown(t[x], 1);
        return res;
    }
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    int res = 0;
    if (l <= mid) res += ask(l, r, x << 1);
    if (r > mid) res += ask(l, r, x << 1 | 1);
    pushup(x);
    return res;
}
int main()
{
    int T; cin >> T;
    while (T--) {
        int n, m; scanf("%d %d", &n, &m);
        build(1, n); //建树区间还是习惯从1开始, 所以后续操作也要做相应的映射
        while (m--) {
            int op, x, y; scanf("%d %d %d", &op, &x, &y);
            if (op == 1) {
                L = n, R = 0; 
                int temp = y; //这里因为y要传递引用, 如果最后y值不变, 表示一朵花都没放.
                modify(x + 1, n, y); 
                if (y != temp) printf("%d %d\n", L - 1, R - 1);
                else printf("Can not put any one.\n");
            }
            else printf("%d\n", ask(x + 1, y + 1));
        }
        printf("\n");
    }
    return 0;
}


/* 解法二: 二分 */
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E4 + 10;
struct node {
    int l, r;
    int val;
    int lazy;
}t[N << 2];
void pushdown(node& op, int lazy) {
    op.val = lazy * (op.r - op.l + 1);
    op.lazy = lazy;
}
void pushdown(int x) {
    if (t[x].lazy == -1) return;
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = -1;
}

void pushup(int x) { t[x].val = t[x << 1].val + t[x << 1 | 1].val; }

void build(int l, int r, int x = 1) {
    t[x] = { l, r, 1, -1 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    pushup(x);
}

void modify(int l, int r, int c, int x = 1) {
    if (l <= t[x].l && r >= t[x].r) {
        pushdown(t[x], c);
        return;
    }
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(x);
}

int ask(int l, int r, int x = 1) { //查询[l, r]区间的空花瓶数目
    if (l <= t[x].l && r >= t[x].r) return t[x].val;
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    int res = 0;
    if (l <= mid) res += ask(l, r, x << 1);
    if (r > mid) res += ask(l, r, x << 1 | 1);
    return res;
}

int n, m;
int getindex(int a, int c) { //找到[a, n]区间, 第c个可以放花瓶的位置
    int l = a, r = n;
    while (l < r) {
        int mid = l + r >> 1;
        if (ask(a, mid) >= c) r = mid;
        else l = mid + 1;
    }
    return l;
}
int main()
{
    int T; cin >> T;
    while (T--) {
        scanf("%d %d", &n, &m);
        build(1, n);
        while (m--) {
            int k, x, y; scanf("%d %d %d", &k, &x, &y);
            if (k == 1) {
                int cou = ask(x + 1, n);
                if (!cou) printf("Can not put any one.\n");
                else {
                    int L = getindex(x + 1, 1);
                    int R = getindex(x + 1, min(y, cou)); //注意要和cou取一个min
                    modify(L, R, 0);
                    printf("%d %d\n", L - 1, R - 1);
                }
            }
            else {
                int res = y - x + 1 - ask(x + 1, y + 1);
                modify(x + 1, y + 1, 1);
                printf("%d\n", res);
            }
        }
        puts("");
    }
    return 0;
}

一杯茶, 一包烟, 一个BUG改一天 TwT

kuangbin线段树专题点这里!!!

END

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值