4144.畜栏保留问题【贪心+优先队列】

本文介绍了如何解决畜栏保留问题,通过贪心策略和优先队列实现。首先将牛按开始时间排序,然后用优先队列维护畜栏,确保最小结束时间。贪心策略保证了畜栏数的最小化,并通过反证法证明其正确性。最后,文章给出了代码实现和优先队列的相关知识点。

4144.畜栏保留问题

总时间限制: 1000ms 内存限制: 65536kB

描述

农场有N头牛,每头牛会在一个特定的时间区间[A, B](包括A和B)在畜栏里挤奶,且一个畜栏里同时只能有一头牛在挤奶。现在农场主希望知道最少几个畜栏能满足上述要求,并要求给出每头牛被安排的方案。对于多种可行方案,主要输出一种即可。

输入

输入的第一行包含一个整数N(1 ≤ N ≤ 50, 000),表示有N牛头;接下来N行每行包含两个数,分别表示这头牛的挤奶时间[Ai, Bi](1 ≤ A≤ B ≤ 1, 000, 000)。

输出

输出的第一行包含一个整数,表示最少需要的畜栏数;接下来N行,第i+1行描述了第i头牛所被分配的畜栏编号(从1开始)。

样例输入

5
1 10
2 4
3 6
5 8
4 7

样例输出

4
1
2
3
2
4

分析

贪心策略

最大限度利用已有的畜栏。

  1. 把n头牛按照开始时间排序;
  2. 为第1头牛新建一个畜栏,记录其结束时间;
  3. 对于第i头牛,找到已有的畜栏中结束时间最早的,并与它自己的开始时间比较。
  4. 如果开始时间大于(注意这里是严格大于,之前自以为是大于等于然后一直WA嘤嘤嘤┭┮﹏┭┮)最早的结束时间,就把牛i安排到那个畜栏里面,把原来的牛j“丢出来”;如果小于等于,就新建一个畜栏。更新结束时间。

贪心策略正确性的证明

假设用以上贪心策略对前n头牛的安排方案至少和最优策略用的畜栏数相同,并且该方案具有最小的最早结束时间。

  1. 当n=1时,结论显然成立;
  2. 假设当n=k时也成立,那么对于k+1头牛:
    ① 证明贪心策略对前n头牛的安排方案至少和最优策略用的畜栏数相同:
    (反证法)若畜栏数不是最优的,已知k头牛的最优策略的畜栏数就是上述贪心策略得到的,那么前k+1头牛的最优策略不会小于这个数;
    又因为结论不成立,所以一定是——
    贪心策略:放入第k+1头牛的时候,它的开始时间Ak+1小于前k头牛贪心策略得到的最早结束时间Tk,新建了一个畜栏;
    最优策略:放入第k+1头牛的时候,它的开始时间大于等于前k头牛最优策略得到的最早结束时间T’k,放入了已有的畜栏;
    也就是T’k<=Ak+1<Tk——这与归纳假设中前k头牛的贪心策略具有最小的最早结束时间矛盾!
    ② 证明贪心策略具有最小的最早结束时间:
    当第k+1头牛的开始时间小于最小的最早结束时间,就新建一个畜栏,这是最早结束时间更新为第k+1头牛的结束时间(如果它比原有的更小),或者保持原来的最小最早结束时间不变;
    否则,放入相应的畜栏,在所有的畜栏的结束时间中找到最小的,更新最早结束时间。
    上述每一种更新,都保证了最早的结束时间是最小的。

贪心的实现

  1. 按照开始时间排序所有的牛;
  2. 用优先队列维护已有畜栏,保证堆顶的畜栏具有最小的最早结束时间;
    对于一头新的牛,要么把原有的牛弹出,再放入自己(相当于更新了原有的畜栏的状态),要么直接放入自己(相当于新建了一个畜栏)。
  3. 注意此时的运算符重载在结构体内部

代码及解析

#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 50010
struct cattle//定义结构体,l,r分别表示牛挤奶的开始、结束时间,id表示第i头牛
{
	int l, r, id;
	bool operator < (const cattle& a)const
	{
		return r > a.r;//保证优先队列的队首是结束时间最早的畜栏
	}
};
priority_queue <cattle> q;//建立优先队列
cattle a[MAXN];//保存每一头牛的信息
int use[MAXN];//保存每一头牛被放入的畜栏编号
bool mycmp(cattle a, cattle b)//自定义排序,按照开始时间从小到大
{
	if (a.l == b.l)return a.r < b.r;
	return a.l < b.l;
}

主函数:

int main()
{
	int n;//牛的总数
	int ans = 0;//畜栏的总数
	cin >> n;
	memset(use, 0, sizeof(use));//归零(害其实关系不大)
	while (!q.empty())q.pop();//清空
	for (int i = 0; i < n; i++)
	{
		cin >> a[i].l >> a[i].r;
		a[i].id = i;
	}
	sort(a, a + n, mycmp);//把n头牛按照开始时间从小到大进行排序
	for (int i = 0; i < n; i++)
	{
		if (!q.empty() && q.top().r < a[i].l)//如果当前有畜栏并且可以用
		{
			use[a[i].id] = use[q.top().id];//把牛i放到这个畜栏里面
			q.pop();//把原来的牛“丢出去”
		}
		else
		{
			ans++;//否则新建一个畜栏
			use[a[i].id] = ans;//把牛i放到新的畜栏里面
		}
		q.push(a[i]);//把牛i放到原队列里面
	}
	cout << ans << endl;
	for (int i = 0; i < n; i++)
		cout << use[i] << endl;
	return 0;
}

知识点

优先队列

  1. 基本概念
    可以按照自定义的方式(数据的优先级)对队列中的数据动态排序
    每次pop,push操作,队列都会根据优先级动态调整
  2. 优先级
    默认使用<来确定数据之间的优先级关系
    也可以由用户自定义
    自定义的方式有两种:1. 不需要结构体(如下);2.在结构体内嵌运算符重载
    priority_queue<int,vector<int>,less<int>>s;//定义优先级队列s,less表示按照递减(从大到小)的顺序插入元素 priority_queue<int,vector<int>,greater<int>>s;//定义优先级队列s,greater表示按照递增(从小到大)的顺序插入元素 priority_queue<int> p;//直接定义,默认为顺序递减
  3. 基本操作:
    empty(); 队列为空返回1
    pop(); 出队
    push(); 入队
    top(); 返回队列中优先级最高的元素
    size(); 返回队列中元素的个数

基于多关键字的结构体比较
4. 内嵌运算符重载
决定了优先队列中的“优先级”规则

struct cattle//定义结构体,l,r分别表示牛挤奶的开始、结束时间,id表示第i头牛
{
	int l, r, id;
	bool operator < (const cattle& a)const
	{
		return r > a.r;//保证优先队列的队首是结束时间最早的畜栏
	}
};

另一种定义方式

struct node
{
    friend bool operator< (node n1, node n2)
    {
        return n1.priority < n2.priority;
    }
    int priority;
    int value;
}; 
  1. 外置比较函数
    比如传参到sort()函数里面使用
bool mycmp(cattle a, cattle b)//自定义排序,按照开始时间从小到大
{
	if (a.l == b.l)return a.r < b.r;
	return a.l < b.l;
}

sort(a, a + n, mycmp);//把n头牛按照开始时间从小到大进行排序

芜湖~第一篇题解写完啦!

### 畜栏保留问题算法与编程实现 畜栏保留问题(Stable Marriage Problem 或相关变种问题)通常涉及分配资源或匹配对象的问题。以下是一个基于贪心算法和动态规划思想的解决方案,假设问题的目标是最小化某种代价或最大化某种收益。 #### 问题描述 给定一组畜栏和一组牛,每头牛对每个畜栏有一个偏好值,或者每个畜栏对每头牛有一个接受能力值。目标是将牛分配到畜栏中,使得满足某种条件下的最优解。 #### 解决方案 以下是一个基于排序和贪心策略的实现[^1]: ```python def assign_cows_to_stalls(preferences, k): """ 分配牛到畜栏的函数。 :param preferences: 牛对畜栏的偏好列表,二维数组 :param k: 每个畜栏的最大容量 :return: 分配结果 """ n = len(preferences) # 牛的数量 m = len(preferences[0]) # 畜栏的数量 # 初始化分配状态 stall_assignments = [[] for _ in range(m)] for cow_index, preference in enumerate(preferences): # 对当前牛的偏好进行排序 sorted_stalls = sorted(range(m), key=lambda x: preference[x]) # 尝试将牛分配到最偏好的畜栏 for stall in sorted_stalls: if len(stall_assignments[stall]) < k: stall_assignments[stall].append(cow_index) break return stall_assignments # 示例输入 preferences = [ [3, 1, 2], # 第一头牛对三个畜栏的偏好 [2, 3, 1], [1, 2, 3] ] k = 2 # 每个畜栏最多容纳两头牛 # 调用函数 result = assign_cows_to_stalls(preferences, k) # 输出结果 for i, cows in enumerate(result): print(f"Stall {i + 1} has cows: {cows}") ``` #### 算法说明 上述代码实现了基于偏好排序的分配逻辑。对于每头牛,按照其对畜栏的偏好顺序尝试分配,直到找到一个未满的畜栏为止。如果所有畜栏都已满,则该牛无法被分配。 #### 动态规划思路 如果问题变为需要最小化某种代价(例如分配不均的惩罚),可以使用动态规划解决。以下是动态规划的核心思想[^3]: 1. 定义状态:`dp[i][j]` 表示前 `i` 头牛分配到前 `j` 个畜栏的最小代价。 2. 转移方程:根据当前牛是否分配到某个畜栏更新状态。 3. 初始条件:`dp[0][j] = 0`,表示没有牛时代价为零。 #### 相关公式 动态规划的状态转移方程如下: \[ dp[i][j] = \min(dp[i-1][j], dp[i][j-1] + cost(i, j)) \] 其中,`cost(i, j)` 表示将第 `i` 头牛分配到第 `j` 个畜栏的代价。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值