acwing 111. 畜栏预定 贪心经典:线段不重叠的最小分组
有N头牛在畜栏中吃草。
每个畜栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个畜栏。
给定N头牛和每头牛开始吃草的时间A以及结束吃草的时间B,每头牛在[A,B]这一时间段内都会一直吃草。
当两头牛的吃草区间存在交集时(包括端点),这两头牛不能被安排在同一个畜栏吃草。
求需要的最小畜栏数目和每头牛对应的畜栏方案。
输入格式
第1行:输入一个整数N。
第2…N+1行:第i+1行输入第i头牛的开始吃草时间A以及结束吃草时间B,数之间用空格隔开。
输出格式
第1行:输入一个整数,代表所需最小畜栏数。
第2…N+1行:第i+1行输入第i头牛被安排到的畜栏编号,编号是从1开始的 连续 整数,只要方案合法即可。
数据范围
1≤N≤50000
,
1≤A,B≤1000000
输入样例:
5
1 10
2 4
3 6
5 8
4 7
输出样例:
4
1
2
3
2
4
推荐y总讲题视频 : https://www.acwing.com/video/88/
就是给定很多条线段(可能有重叠), 把他们分组,每组要求组内无重叠线段,
问最小多少组 ?
线段分组 : 有很多条可能重叠的线段,求最小分组,每组里面没有重叠线段
第一组 : -------- -------- --- (组内不重叠)
第二组 : --- ------ ---------- (组内不重叠)
第三组 : ------------------ ---- (组内不重叠)
考虑贪心 :
- 先把所有线段按起点递增排序,起点相同按终点递增排序
- 然后扫描每个线段,把第一个线段入堆(因为至少要一个组),
- 剩下的线段如果和堆里的线段都
有
交集,就必须新建一个组 - 同时更新这组
(堆顶)
的末端点
假设按照以上做法求出来的最小值是m
假设最优解是n
, 则n<m
观察到到达m
是从1
开始每次+1
的
在枚举的时候,一定存在某个时刻出现n
变成n+1
的过程
当某一个区间的起点
大于其他的某个终点
时, 才要新建一个畜栏
1. -------------
2. -------
3. ------- (3号需要新建畜栏)
4. ---------
代码如下
#define debug
#ifdef debug
#include <time.h>
#include "/home/majiao/mb.h"
#endif
#include <iostream>
#include <algorithm>
#include <vector>
#include <string.h>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <math.h>
#define MAXN ((int)1e5+7)
#define ll long long int
#define INF (0x7f7f7f7f)
#define fori(lef, rig) for(int i=lef; i<=rig; i++)
#define forj(lef, rig) for(int j=lef; j<=rig; j++)
#define fork(lef, rig) for(int k=lef; k<=rig; k++)
#define QAQ (0)
using namespace std;
#define show(x...) \ do { \ cout << "\033[31;1m " << #x << " -> "; \ err(x); \ } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<typename T, typename... A>
void err(T a, A... x) { cout << a << ' '; err(x...); }
namespace FastIO{
char print_f[105];
void read() {}
void print() { putchar('\n'); }
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
x = 0;
char ch = getchar();
ll f = 1;
while (!isdigit(ch)) {
if (ch == '-') f *= -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
x *= f;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth) {
ll p3=-1;
if(x<0) putchar('-'), x=-x;
do{
print_f[++p3] = x%10 + 48;
} while(x/=10);
while(p3>=0) putchar(print_f[p3--]);
putchar(' ');
print(oth...);
}
} // namespace FastIO
using FastIO::print;
using FastIO::read;
int n, m, Q, K;
/* 线段分组 : 有很多条可能重叠的线段,求最小分组,每组里面没有 第一组 : -------- -------- --- (组内不重叠) 第二组 : --- ------ ---------- (组内不重叠) 第三组 : ------------------ ---- (组内不重叠) */
struct Node {
int lef, rig, id; //要记录id
} a[MAXN];
bool cmplef(Node& A, Node& B) { //开始先按左端点sort
return A.lef == B.lef ? A.rig < B.rig : A.lef < B.lef;
}
struct cmpque { //堆里结束点最小的在堆顶
bool operator () (const Node& A, const Node& B) const {
return A.rig > B.rig;
}
} ;
#define TOP (q.top())
#define TRIG (q.top().rig)
int main() {
#ifdef debug
freopen("test", "r", stdin);
clock_t stime = clock();
#endif
read(n);
for(int i=1; i<=n; i++) {
read(a[i].lef, a[i].rig);
a[i].id = i; //别忘了记录id
}
sort(a+1, a+1+n, cmplef); //先按开始时间排序
//小根堆,堆顶是最早结束的牛
priority_queue<Node, vector<Node>, cmpque> q;
int ans[MAXN] = { 0 }, timer = 0/*畜栏id递增*/, cnt = 0/*记录需要几个畜栏*/;
/* 这里贪图方便,我把每个入堆以后的node.lef设置成畜栏的id, 因为入堆后lef不影响,或者说未被使用,反正空着也是空着, 就直接用来存畜栏的id了 用timer表示畜栏id */
for(int i=1; i<=n; i++) {
if(q.empty() || TRIG>=a[i].lef) { //当前没有畜栏空着,就新建一个
cnt ++;
a[i].lef = ++ timer; //把堆内的node.lef用来保存畜栏id
q.push(a[i]);
ans[a[i].id] = timer; //记录答案
} else {
Node no = TOP; //堆顶的牛吃完了,这个畜栏就空了
q.pop();
no.rig = a[i].rig; //第i头牛进去吃
q.push(no);
ans[a[i].id] = no.lef;
}
}
printf("%d\n", cnt);
for(int i=1; i<=n; i++)
printf("%d\n", ans[i]);
#ifdef debug
clock_t etime = clock();
printf("rum time: %lf 秒\n",(double) (etime-stime)/CLOCKS_PER_SEC);
#endif
return 0;
}