Loading

CF427B Prison Transfer

相信大家都看得懂题意,所以就不再多说了。

为方便表示,下文中统一用 \(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;
}


后记

我是真没想到分块和线段树差不多快,可能我人傻常数大吧。

posted @ 2021-05-15 17:46  Last_order  阅读(67)  评论(0)    收藏  举报