按区间右端点升序排序,区间缺树时贪心靠右侧放置以最大化后续重叠复用。
OJ: roj
题目 ID: 1423
难度:普及
标签:贪心区间问题
创建: 2026-09-30 09:25
更新: 2026-10-05 23:22
题目描述
编号
思路
经典区间贪心:把所有区间按右端点
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-07-05 21:47
# update_at: 2026-07-05 21:47
import sys
# 区间三元组类型:(起始位置 b, 结束位置 e, 至少种树数 t)
Interval = tuple[int, int, int]
def min_trees(intervals: list[Interval], max_pos: int) -> int:
"""计算满足所有区间要求所需的最少树木总数。
按区间右端点从小到大贪心排序:为了尽量复用给后续区间,
每个区间缺少的树木优先从右向左紧挨着右端点放置。
"""
# 状态数组:planted[pos] 为 True 表示位置 pos 已种树(1-indexed)
planted = bytearray(max_pos + 1)
total_trees = 0
# 优先按结束位置 e 升序排序
for b, e, t in sorted(intervals, key=lambda iv: iv[1]):
# 统计当前区间 [b, e] 内已经种了多少棵树
current_count = sum(planted[p] for p in range(b, e + 1))
need = t - current_count
if need <= 0:
continue
# 从右端点向左贪心补齐缺少的树
for p in range(e, b - 1, -1):
if not planted[p]:
planted[p] = 1
total_trees += 1
need -= 1
if need == 0:
break
return total_trees
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data, None)
if n is None:
return
h = next(data) # 居民需求条数(区间数)
intervals: list[Interval] = [(next(data), next(data), next(data)) for _ in range(h)]
ans = min_trees(intervals, n)
print(ans)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 23:23
* update_at: 2026-10-05 23:23
*/
// main.cpp:「一本通 1.1 例 2」种树,区间贪心:按右端点排序,缺树时从右往左补种。
// 输出最少种树数以及每棵树的位置。
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 30005; // 地块数上限
const int MAXM = 5005; // 居民需求数上限
// 一条居民需求:区间 [b, e] 内至少种 t 棵树
struct Demand {
ll b, e, t;
};
ll n, m;
Demand d[MAXM];
bool planted[MAXN]; // planted[pos] = true 表示位置 pos 已种树
// 贪心比较:按区间右端点 e 从小到大排序
bool cmp_e(const Demand &a, const Demand &b) {
return a.e < b.e;
}
int main() {
scanf("%lld %lld", &n, &m);
for (int i = 1; i <= m; i++)
scanf("%lld %lld %lld", &d[i].b, &d[i].e, &d[i].t);
// 右端点小的区间先处理,后面的区间才能复用靠右种的树
sort(d + 1, d + m + 1, cmp_e);
ll total = 0; // 总共种下的树数
for (int i = 1; i <= m; i++) {
// 先统计当前区间 [b, e] 内已经种了多少棵
ll has = 0;
for (ll p = d[i].b; p <= d[i].e; p++)
if (planted[p])
has++;
// 还缺 need 棵,从右端点向左找空位补种,
// 靠右种最容易被后面的区间复用
ll need = d[i].t - has;
for (ll p = d[i].e; p >= d[i].b && need > 0; p--) {
if (!planted[p]) {
planted[p] = true;
total++;
need--;
}
}
}
printf("%lld\n", total);
// 逐棵输出位置,位置之间用空格分隔,行尾不留多余空格
bool first = true;
for (ll p = 1; p <= n; p++)
if (planted[p]) {
if (first) {
printf("%lld", p);
first = false;
} else
printf(" %lld", p);
}
printf("\n");
return 0;
}