codeforces898D Alarmclock

有些类似线段覆盖的贪心。蛮巧妙的。


题意大概是这样的,
n 个闹钟,第i个会在第 ai 时刻开始叫 m 分钟,如果在某一段区间[L,L+m]有超过 k 个闹钟响小明就会被吵醒,求最少关掉多少闹钟会使得小明不被吵醒,并且在区间外开始响的闹钟不计入区间中。
1kn2105, 1ai,m106
其实我第一反应是有些惊讶,毕竟 ai 竟然不是小于 109 。想到了什么?没错,值域思想。


先来理性(玄学)分析一波。一个直观的想法是,我们按照重叠最多的顺序排序,但是这样是有问题的,毕竟如果这么一排,就会导致左右两边分开,反而会使情况更糟。
但是如果我们从左往右扫就不一样了。只要碰到会吵醒的情况,我们就弹出,这样能够保证区间是连续的。并且,由于区间一定会出现问题,所以我们不能就此放置不管!那关哪个呢?我们选择最右面的,因为这样会让剩余的能关的更少。
而实现也以此分成两大派别。


(伪)滑动窗口派

简洁而明了的实现,类似滑动窗口滚一遍。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <deque>
using namespace std;
int n, m, k, a[200003], cnt;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>m>>k;
    for(int i = 1; i <= n; ++i) cin>>a[i];
    sort(a + 1, a + n + 1);
    deque<int> dq;
    for(int i = 1; i <= n; ++i) {
        while(dq.size() && dq.front() <= a[i] - m) dq.pop_front();
        dq.push_back(a[i]);
        while(dq.size() >= k) ++cnt, dq.pop_back(); 
    }
    cout<<cnt<<endl;
    return 0;
}

由于双端队列末尾恰好是最右面的,所以可以直接弹走。


BIT派
之前就提到过值域思想,那么我们可以将值域维护成一个差分数组,然后利用树状数组实现前缀和的查询和修改。

#include <iostream>
#include <cstdio>
#define N 1000003
using namespace std;
int n, m, k;
int bit[N];
inline int lowbit(int x) { return x & -x; }
inline void update(int x, int val) { 
    while(x < N) {
        bit[x] += val;
        x += lowbit(x);
    } 
}
inline int query(int x) {
    int ans = 0;
    while(x) {
        ans += bit[x];
        x -= lowbit(x);
    }
    return ans;
}
inline int getsum(int l, int r) {
    return query(r) - query(l);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>m>>k;
    int tmp = 0, ans = 0;
    for(int i = 1; i <= n; ++i) cin>>tmp, update(tmp, 1);
    for(int i = 0; i < N; ++i) {
        if(i < m && getsum(0, i) == k) update(i, -1), ans++;
        if(i >= m && getsum(i - m, i) == k) update(i, -1), ans++;
    }
    cout<<ans<<endl;
    return 0;
}

参考代码:
http://codeforces.com/contest/898/submission/33296415
http://codeforces.com/contest/898/submission/33292393

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值