CF427B Prison Transfer
题目链接:Link
相信大家都看得懂题意,所以就不再多说了。
为方便表示,下文中统一用 \(n\) 表示囚犯总数,\(t\) 表示囚犯犯罪水平应不高于 \(t\), \(c\) 表示要转移 \(c\) 个连续的囚犯 。
由于这道题数据比较小可以乱搞,所以下面提供三种能过的做法。
0. 暴力
暴力地枚举每一个长度为 \(c\) 的区间,求区间的最大值,统计符合题意的区间数量。
时间复杂度为 \(O(n - c + 1) \times O(c)\) 约为 \(n ^ 2\),妥妥 T 飞,就不放代码了。
1. 模拟
依据题意模拟即可。
线性的扫一遍,若当前的数字小于等于 \(t\) ,则计数器 +1 ,否则计数器清空。
在线处理的话,可以将空间复杂度降到最小,时间复杂度为 \(O(n)\)。
#include <cstdio>
int n, t, c, k, cnt, ans;
int main(){
scanf("%d %d %d", &n, &t, &c);
for(int i = 1; i <= n; i++){
scanf("%d", &k);
if(k <= t){
if(++cnt >= c) ans++; //囚犯数量足够,则开始累加答案
}
else cnt = 0; //若出现不满足的囚犯,则清空计数
}
printf("%d\n", ans);
return 0;
}
2. 线段树
其实刚看题的时候我并没有想到线段树,后来发现 \(n \le 2 \times 10^5\) 的数据实在是太宽松了。
我们只需要维护一段区间的最大值,然后进行 \(n - c + 1\) 次区间查询即可,由于线段树单次区间查询的复杂度为 \(O(\log n)\) ,所以复杂度约为 \(O(n \log n)\) ,还是比较稳的。
/*珂爱的线段树*/
#include <cstdio>
#include <algorithm>
#include <iostream>
#define maxn 200005
using namespace std;
int sum, n, T, c, a[maxn];
struct Tree_Node{
int val, l, r;
}tr[maxn << 2]; //我一般习惯开 4 倍空间,防止越界
inline void update(int node){ //这个函数可以不写,直接放在 build 函数中
tr[node].val = max(tr[node << 1].val, tr[node << 1 | 1].val);
}
void build(int s, int t, int node){
tr[node].l = s, tr[node].r = t;
if(s == t){tr[node].val = a[s]; return;}
int mid = s + t >> 1;
build(s, mid, node << 1);
build(mid + 1, t, node << 1 | 1);
update(node);
}
int ask_max(int s, int t, int node){ //查询区间最大值,与 t 比较
int ans = 0;
if(s <= tr[node].l && t >= tr[node].r){
return tr[node].val;
}
int mid = tr[node].l + tr[node].r >> 1;
if(s <= mid) ans = max(ans, ask_max(s, t, node << 1));
if(t > mid) ans = max(ans, ask_max(s, t, node << 1 | 1));
return ans;
}
int main(){
scanf("%d %d %d", &n, &T, &c);
for(int i = 1; i <= n; i++){
scanf("%d",&a[i]);
}
build(1, n, 1);
for(int i = 1; i + c - 1 <= n; i++){
if(ask_max(i, i + c - 1, 1) <= T) sum++; // 查询每一个长度为 c 的区间
}
printf("%d",sum);
return 0;
}
3. 分块
请再思考一下 \(2 \times 10^5\) 的数据还能支持什么样的时间复杂度呢?
显然,复杂度为 \(O(n \sqrt{n})\) 的分块常数别太大也是能过的。(大家应该都会分块吧)
/*分块大法*/
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
int block, num, n, t, c, ans;
// block : 块长 , num : 块的数量 , ans : 最终的答案,即有几个长度为 c 的区间满足条件 ;
int belong[200005], a[200005];
// belong[i] 记录下表为 i 的点在哪个块中。
int l[1005], r[1005], maxx[1005];
// l[i] 记录第 i 个块左边界的下标, r[i] 记录第 i 个块右边界的下标;
// maxx[i] 表示第 i 个块的最大值。
inline void build(){
block = sqrt(n);
num = n / block;
if(n % block) num++;
for(int i = 1; i <= n; i++){
belong[i] = (i - 1) / block + 1;
}
for(int i = 1; i <= num; i++){
l[i] = block * (i - 1) + 1,
r[i] = block * i;
}
r[num] = n;
for(int i = 1; i <= num; i++){
for(int j = l[i]; j <= r[i]; j++){
maxx[i] = max(a[j], maxx[i]);
}
}
}
inline int ask_max(int x, int y){
int maxn = -1; // maxn 用于记录查询的这个区间的最大值
/*处理整块的部分*/
for(int i = belong[x] + 1; i < belong[y]; i++){
maxn = max(maxn, maxx[i]);
}
/*处理非整块的部分*/
if(belong[x] != belong[y]){
for(int i = x; i <= r[belong[x]]; i++){
maxn = max(maxn, a[i]);
}
for(int i = l[belong[y]]; i <= y; i++){
maxn = max(maxn, a[i]);
}
}
else{
for(int i = x; i <= y; i++){
maxn = max(maxn, a[i]);
}
}
return maxn;
}
signed main(){
scanf("%d %d %d", &n, &t, &c);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
}
build();
for(int i = 1; i + c - 1 <= n; i++){
if(ask_max(i, i + c - 1) <= t) ans++; //查询每个长度为 c 的区间的最大值。
}
printf("%d\n", ans);
return 0;
}
后记
我是真没想到分块和线段树差不多快,可能我人傻常数大吧。

浙公网安备 33010602011771号