【洛谷】种树——区间与贪心的不解之缘

578 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

一条街的一边有几座房子,因为环保原因居民想要在路边种些树。

路边的地区被分割成块,并被编号成 1,2,,n1,2,…,n。每个部分为一个单位尺寸大小并最多可种一棵树。

每个居民都想在门前种些树,并指定了三个号码 betb,e,t。这三个数表示该居民想在地区 bbee 之间(包括 bbee)种至少 tt 棵树。

居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。

输入格式

输入的第一行是一个整数,代表区域的个数 nn

输入的第二行是一个整数,代表房子个数 hh

33 到第 (h+2)(h + 2) 行,每行三个整数,第 (i+2)(i + 2) 行的整数依次为 bi,ei,tib_i, e_i, t_i ,代表第 ii 个居民想在 bib_ieie_i之间种至少 tit_i 棵树。

输出格式

输出一行一个整数,代表最少的树木个数。

输入输出样例

输入

9
4
1 4 2
4 6 2
8 9 2
3 5 2

输出

5

数据规模与约定

对于 100% 的数据,保证:

1n3×1041h5×1031biein1tieibi+11≤n≤3×10^4\\ 1≤h≤5×10^3\\ 1≤b_i≤e_i≤n\\ 1≤t_i≤e_i−b_i+1\\

题目思路详解

用什么方法?

审题时一定要养成一个习惯:看数据规模

通过数据规模,我们其实就可以大致的推断出这道题怎么求解了。毕竟,出题人出了个 n 106n~10^6 的题,总不能让你用暴力for循环就轻易通关吧,这时我们就要考虑有没有 时间复杂度 nlgnnlgn 的解法了。

比如说这道题,四次方的规模,看上去用 n2n^2 复杂度的解法就能搞定。

想一想有什么解法...动态规划?由于给的请求是以区间的形式存在的,所以不太好搞状态转移....

贪心?

是否可以这样想:要使种的树最少,只要让树尽可能种在区间重叠度高的地方就行了?

怎么个种法?想想贪心的重要方法之一:排序。

我们把所有的区间按照终点从小到大排序。然后我们从左到右对于区间进行填补。我们维护如下的子问题:

排序后,每个区间左边和右边都会有一些重叠区域,我们种到这些重叠区域是最好的。假设我们种好了前 i-1 个区间并到达了第i个区间,这时即使左边有重叠区域,我们肯定也不用再从左边种了,为什么?因为这个子问题的结构就是:种好了前 i-1 个区间,那么即使种在左边的重叠区域,由于左边的区间已经满足要求,所以还是等效于只对当前的区间起到贡献。但是由于后面的区间我们还没有触及,从最右边往左边一个一个种树就可以尽量重在多的重叠区域里,并且对尽量多的区间起到贡献,达到贪心的效果。

再提醒几句

给区间排序时,如果两个区间的终点相同,那起点大的拍左边还是起点小的排左边?其实都一样。各位结合上面的分析仔细想想就明白了。具体而言我是按起点从小到大的。

完整代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cctype>
#include <string>
#include <cmath>
#include <cstring>
#include <queue>
#include <numeric>
#define fru(a, b, c) for (int a = b; a <= c; a++)
#define frd(a, b, c) for (int a = b; a >= c; a--)
#define fr(a, b) for (int a = 0; a < b; a++)
#define pb push_back
#define mp make_pair
#define sof sizeof
using namespace std;
using ll = long long;
using db = double;
const int maxn = 30000 + 5;
int arr[maxn];
int ask[maxn][3];
int cmp(const void *a, const void *b)
{
   if (*((int *)a + 1) != *((int *)b + 1))
      return *((int *)a + 1) - *((int *)b + 1);
   else
      return *(int *)a - *(int *)b;
}
int main()
{
   int n;
   cin >> n;
   int h;
   cin >> h;
   for (int i = 0; i < h; i++)
   {
      int b, e, t;
      cin >> b >> e >> t;
      ask[i][0] = b;
      ask[i][1] = e;
      ask[i][2] = t;
   }
   qsort(ask, h, sizeof(int) * 3, cmp);
   for (int i = 0; i < h; i++)
   {
      int cnt = 0;
      for (int j = ask[i][0]; j <= ask[i][1]; j++)
      {
         if (arr[j])
            cnt++;
      }
      for (int j = ask[i][1]; j >= ask[i][0] && ask[i][2]>cnt ; j--)
      {
         if (!arr[j])
         {
            arr[j] = 1;
            cnt++;
         }
      }
   }
   int res = 0;
   for (int i = 0; i <= n; i++)
   {
      if (arr[i])
         res++;
   }
   cout << res;
}

总结

  • 遇到多个区间,可以考虑贪心算法。
  • 审题记得看数据规模来推断解法。