算法
(贪心) $O(nlogn)$
我们先介绍做法,再证明其正确性。
算法步骤:
- 将所有牛按开始吃草的时间排序;
- 用小根堆维护当前所有畜栏的最后一头牛的吃草结束时间;
- 如果当前的牛可以安排在堆顶的畜栏中,则将其安排进去,否则就新建一个畜栏;
证明:
反证法,假设存在一种方案,使得需要的畜栏数量更少,记其需要的畜栏数量是 $m$。
考虑在上述做法中,第一次新建第 $m + 1$ 个畜栏的时刻,不妨设当前处理的是第 $i$ 头牛。
由于所有牛是按开始时间从小到大排好序的,所以现在前 $m$ 个畜栏中最后一头牛的开始时间一定小于等于第 $i$ 头牛的开始时间。
而且前 $m$ 个畜栏中最小的结束时间大于等于第 $i$ 头牛的开始时间,所以前 $m$ 个畜栏里最后一头牛的吃草区间一定都包含第 $i$ 头牛的开始时间,所以我们就找到了 $m + 1$ 个区间存在交集,所以至少需要 $m + 1$ 个畜栏,矛盾。
所以上述做法可以得到最优解,证毕。
时间复杂度
- 排序的时间复杂度是 $O(nlogn)$。
- 依次枚举每头牛的过程中,只涉及到常数次堆的操作,时间复杂度至多是 $O(logn)$。
所以总时间复杂度是 $O(nlogn)$。
C++ 代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 50010;
int n;
int id[N];
pair<PII, int> cows[N];
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
{
cin >> cows[i].first.first >> cows[i].first.second;
cows[i].second = i;
}
sort(cows, cows + n);
priority_queue<PII, vector<PII>, greater<PII>> heap;
for (int i = 0; i < n; i ++ )
{
if (heap.empty() || heap.top().first >= cows[i].first.first)
{
PII stall = {cows[i].first.second, heap.size()};
id[cows[i].second] = stall.second;
heap.push(stall);
}
else
{
auto stall = heap.top();
heap.pop();
stall.first = cows[i].first.second;
id[cows[i].second] = stall.second;
heap.push(stall);
}
}
cout << heap.size() << endl;
for (int i = 0; i < n; i ++ ) cout << id[i] + 1 << endl;
return 0;
}
注解版,供家人们参考
multiset+二分
为什么这里一定要先读入编号的畜栏号 输出的时候是id+1啊友友们
因为题目的组的编号是从1开始的,数组是从0开始的
畜栏预定这道题为什么不用重载运算符呢,小根堆排序的数据类型是自定义的呀
因为pair可以直接用比较运算符进行运算
感谢
有没有大佬帮看一下,思路是不用堆,就每次安排的时候只要找到了当前牛栏的最后结束时间小于当前牛的开始吃草时间就放进去(不用堆优化版本),但是答案不对,找了半天没找到代码的问题,求帮忙看一下,代码哪里不对。
heap.top().first表示右端点???
哦,小根堆真的是太妙了
想问问这样写为什么不对呀 有没有大佬帮忙看一下
for(int i=0;i[HTML_REMOVED]=cows[i].x.x){
heap.push({cows[i].x.y,heap.size()});
id[cows[i].y] = heap.size();
}
else{
heap.pop();
heap.push({cows[i].x.y,heap.size()+1});
id[cows[i].y] = heap.size();
}
}
为啥不直接区间合并
”假设存在一种方案,使得需要的畜栏数量更少“, “考虑在上述做法”两这句话矛盾了吧, 既然有更优的方法, 就不考虑上述做法了啊
y总,差分做可以求出方案吗
我一开始也是想到差分,但是差分只能求出至少需要多少个,但没办法求每头牛是哪个栏
y总,set里面装一个pair,就无法使用upper_bound了吗
我用的multiset就是算不出来
y总,为啥只需要判断堆顶的元素和当前求的大小,堆顶之后有的右端点 <= 当前左端点,不是也可以安排到这个畜栏吗?
自答一下:是按照结束时间排序,hhhh
对滴,根据上面的做法和证明,可以发现我们任意找一个 “右端点 <= 当前左端点”的畜栏,都是可以得到最优解的。所以每次取右端点最小的一个畜栏,也可以得到最优解。
大佬不好意思。。得麻烦您一下
https://www.acwing.com/community/content/601/
有同学已经回答啦。你去看看结果正确了嘛
有看到了,原来是这么弱智的问题233
好滴,加油!
很清晰~
谢谢hh
牛栏编号必须是连续的么 比如第一个答案可以是 1 2 3 2 5吗
必须是连续的,刚刚在题目中添加了这条限制~
请问能不能颠倒顺序
有special judge,方案的顺序任意,只要合法即可。
测试数据我输出是这样的:
4
1
2
3
1
4
提交后显示连测试数据都过不了
最小蓄栏数一样,但是每头牛的蓄栏编号不一样,结果算正确吗?......
对滴,算正确的。后台有程序会答案做判断。