Hello 2023 D. Boris and His Amazing Haircut

原题链接:Problem - D - Codeforces

题意:

给定长度为 n 的数组 A ,代表 Boris 现在的头发长度,和一个长度为 n 的数组 B ,代表他希望的发型的头发长度。理发师手里有 m 把剪刀,每个都只能用一次,剪刀的所剪的高度用 xi 给出。
对于每一把未使用过的推子:
理发师可以选择一个 [l,r] 区间;
将该区间的所有头发 ai 修建为 min(ai,x) 。
请问理发师用手中的这些推子,能不能剪完 Boris 的发型。

思路:显然,若存在a[i]<b[i],则输出NO,因为剪头发只能让a[i]变短,不能让a[i]变长。然后,若a[i]!=b[i]我们才需要操作它,否则不需要。其实,判断完这两步数组a基本没什么用了,以下都是基于对数组b的操作的解释,主要说明的是如何让相同类型剪刀的使用次数变少,并且操作合法。

首先,我们可以选择两个相同的数作为端点,通过样例一可以看出,如果端点内的数都小于等于端点,那么这个修剪区间就是合法的,因为我们可以先将这个区间,都变为小于等于端点的长度,再让区间内小于端点的数通过其它操作变小。反之,若一个区间内有大于端点的数,那么这个修剪区间就是不合法的,因为若你修剪了这个区间,就会使这个大于端点的值变小,它就变不回来了,这样就算a数组再怎么操作也变不成b数组了。

题目中有一个很贴心的样例,我们来看看它:

这两个样例的唯一的区别是什么呢?就是给的剪刀5的数量。我们来看看为什么使用剪刀5数量至少是2个。

首先我们判断下需要修剪的5的地方,就是这: 

还有两个5没做上标记,是因为a[i]也是5,我们不用管它。

所以一开始我们需要3个剪刀5。

我们来逐一检查区间是否合法:

这个区间内所有的数都小于等于它的端点,也就是5,所以这个区间合法,我们可以只用一次操作就能将两个点变成5,这样需要剪刀5的数量-1,变成2了。

接下来我们来看下一个区间:

区间内就一个数,但是这个数比5大,也就是说,我们不能将第三个5合并到第一个区间内,对于数字5,我们剩下了两个区间。最后对于数字5,我们需要修剪的就是两个区间,也就是消耗了两把剪刀。

总结一下,这道题主要考察快速求区间最值,用树状数组、线段树、栈都能写,我用的是树状数组,然后存一下下标判断剪刀是否足够就行,具体实现见代码注释

int lowbit(int x) {
    return x & (-x);
}
void update(int x) {
    int lx, i;
    while (x <= n) {
        h[x] = b[x];
        lx = lowbit(x);
        for (i = 1; i < lx; i <<= 1) h[x] = max(h[x], h[x - i]);
        x += lowbit(x);
    }
}
int query(int x, int y) {
    int ans = 0;
    while (y >= x) {
        ans = max(b[y], ans);
        y--;
        for (; y - lowbit(y) >= x; y -= lowbit(y)) ans = max(h[y], ans);
    }
    return ans;
}
void solve() {
    map<int, int> mp;//我们能用的各个剪刀数 
    map<int, vector<int>> mp2;//需要操作的各个数的下标 
    map<int, int> mp3;//我们需要的剪刀数 
    cin >> n;
    FOR(1, n) h[i] = 0;
    FOR(1, n) cin >> a[i];
    FOR(1, n) {
        cin >> b[i];
        if (a[i] != b[i]) mp2[b[i]].push_back(i);//若a[i]!=b[i]则说明需要操作。 
        update(i);
    }
    cin >> m;
    FOR(1, m) {
        cin >> x;
        mp[x]++; //存能用的各个剪刀数 
    }
    FOR(1, n) {
        if (a[i] < b[i]) {//若存在a[i]<b[i],则输出NO,因为剪头发只能让a[i]变短,不能让a[i]变长 
            no;
            return;
        }
        if (a[i] != b[i]) mp3[b[i]]++;
    }
    for (auto [x, v] : mp2) {
        int p1 = 0, p2 = 1;
        while (p1 < v.size() && p2 < v.size()) {
            if (query(v[p1] + 1, v[p2] - 1) <= x) mp3[x]--, p2++;//若区间合法,就合并下标,并且使需要的剪刀数减1 
            else  p1 = p2++;//若区间不合法,就找下一个区间 
        }
    }
    for (auto [x, y] : mp3) {
        if (mp3[x] > mp[x]) {//若需要的剪刀数超过了拥有的剪刀数,则输出NO 
            no;
            return;
        }
    }
    yes;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值