奥赛一本通 1423 种树

1423 种树

题目大意

给定 $m$ 个范围在 $1 \sim n$ 的区间,每个区间都有一个种树数量的最少要求,求需要种树的最少数量。

知识要点

排序、贪心、二分

解题思路 1

将所有区间按右端点排序,统计前面区间种到本区间的树数量,不满足要求的则尽可能在区间末尾新种一些树,时间复杂度为 $O(m \log m + mn)$。

参考代码 1

#include <bits/stdc++.h>
using namespace std;

struct Region {
    int b, e, t;
} E[5005];
bool flag[30005];
bool cmp(Region A, Region B) {
    return A.e < B.e;
}
int main() {
    int n, m, ans = 0;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++) scanf("%d%d%d", &E[i].b, &E[i].e, &E[i].t);

    sort(E, E + m, cmp);
    for(int i = 0; i < m; i++) {
        int cnt = 0; //已种树的数量
        for(int j = E[i].b; j <= E[i].e && cnt < E[i].t; j++) if(flag[j]) cnt++;
        ans += E[i].t - cnt;
        for(int j = E[i].e; cnt < E[i].t; j--) if(!flag[j]) flag[j] = 1, cnt++;
    }
    printf("%d\n", ans);
    return 0;
}

解题思路 2

将所有种树的位置维护为若干区间,显然这些区间的数量不超过 $m$。统计前面区间种到本区间的树数量时,使用二分查找确定当前包含哪些已经种树的位置区间,然后利用前缀和即可计算。需要新种树时,就是从当前区间末尾开始向前延长,遇到上一个已经种树区间就直接合并即可,时间复杂度为 $O(m \log m)$。

参考代码 2

#include <bits/stdc++.h>
using namespace std;

struct Region {
    int b, e, t;
} E[5005], P[5005]; // P 记录种树的连续区间, t 值为种树总量的前缀和
bool cmp(Region A, Region B) {
    return A.e < B.e;
}

int main() {
    int n, m, ans = 0, num = 0; // num 记录种树区间的个数
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++) scanf("%d%d%d", &E[i].b, &E[i].e, &E[i].t);

    sort(E, E + m, cmp);
    for(int i = 0; i < m; i++) {

        int l = 0, r = num; //二分查找左端点所在区间
        while(l < r) {
            int mid = (l + r + 1) / 2;
            if(P[mid].e <= E[i].b) l = mid;
            else r = mid - 1;
        }

        int cnt = P[num].t - P[l].t; //统计已种树的数量
        if(l + 1 <= num && P[l + 1].b < E[i].b) cnt -= E[i].b - P[l + 1].b;
        if(cnt >= E[i].t) continue;

        P[++num] = {E[i].e + 1, E[i].e + 1, ans}; //创建新的空区间(左闭右开)
        ans += E[i].t - cnt;

        while(cnt < E[i].t) {
            if(P[num].b - P[num - 1].e <= E[i].t - cnt) { //合并到上一区间
                cnt += P[num].b - P[num - 1].e;
                P[num - 1].e = P[num].e;
                num--;
            } else { //延长最后一个种树区间
                P[num].b -= E[i].t - cnt;
                break;
            }
        }
        
        P[num].t = ans; //最后一个区间的前缀和就是答案
    }
    printf("%d\n", ans);
    return 0;
}
posted @ 2026-08-28 17:43  萧西待水  阅读(4)  评论(0)    收藏  举报