奥赛一本通 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;
}

浙公网安备 33010602011771号