有些类似线段覆盖的贪心。蛮巧妙的。
题意大概是这样的,
有
n
个闹钟,第
其实我第一反应是有些惊讶,毕竟
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