愤怒的牛-二分

传送门

本道题目讲的是一个农夫有c头牛和n个隔间,题目会给出每个隔间的位置,因为一个隔间只能装一头牛,所以农夫要给c头牛安排位置,要求任意两头牛的位置的最小值是最大的

解题思路

刚刚看到这个题目的时候,很懵逼,感觉不是很适应,后面就直接去看的题解

题解上是这样说的,二分任意一头牛之间的最小距离,如果满足判断,那么再往更大的值找,如果不满足,那么就往小的值找,听上去好像有点道理哈。emm,判断函数感觉有点绕,自己还说不清楚,后面找时间补

code

#include <iostream>                                    
#include <algorithm>                                   
using namspace std;                                    
int n,c;                                               
int a[100005];                                         
inline int check(int mid)                              
{                                                      
    int k=mid+a[0];                                    
    int ans=1;                                         
    for(int i=1;i<n;i++)                               
    {                                                  
        if(a[i]>=k)                                    
        {                                              
            ans++;                                     
            k=a[i]+mid;                                
        }                                              
    }                                                  
    if(ans>=c)return 1;//说明最大最小距离大于等于mid   
    return 0;                                          
                                                       
}                                                      
int main()                                             
{                                                      
                                                       
    int i,j,k;                                         
    cin>>n>>c;                                         
    for(i=0;i<n;i++)                                   
        cin>>a[i];                                     
    sort(a,a+n);                                       
    int l=a[0],r=a[n-1];                               
    while(l<=r)                                        
    {                                                  
        int mid=(l+r)/2;                               
        if(check(mid))l=mid+1;                         
        else r=mid-1;                                  
    }                                                  
    cout<<l-1<<endl;                                   
    return 0;                                          
}                                                      
                                                       

<think>我们正在解决Farmer John的棚分配问题:给定一条直线上N个隔间的位置(坐标),要放置C头,使得任意两头之间的最小距离尽可能大。即最大化相邻之间的最小距离。 ### 问题分析 - **输入**:N个隔间的位置(坐标)x1, x2, ..., xN(无序),以及的数量C(2<=C<=N)。 - **目标**:将C头放入隔间,使得任意两头之间的最小距离最大化。 - **关键**:最小距离的最大值。这是一个典型的“最小值最大化”问题,通常使用二分查找和贪心策略解决。 ### 算法思路 1. **二分查找答案**:最小距离的取值范围在0到最大坐标差之间。我们在这个范围内二分查找可能的最小距离的最大值。 - 设当前候选的最小距离为`mid`,我们需要判断是否能够放置C头,使得任意两头之间的距离至少为`mid`。 2. **验证函数(贪心策略)**: - 首先对隔间位置排序(因为隔间位置无序)。 - 贪心放置:从第一个隔间开始放置第一头,然后依次检查后续隔间,如果当前隔间与上一头放置的隔间距离>=`mid`,则在此放置一头- 如果能够放置至少C头,则说明`mid`是可行的,可以尝试更大的距离;否则,需要减小距离。 3. **二分框架**: - 初始化:`low = 0`(最小距离至少为0),`high = 最大坐标 - 最小坐标`(即最大可能距离)。 - 每次取`mid = (low + high + 1) / 2`(向上取整,避免死循环)。 - 如果`mid`可行,则`low = mid`;否则`high = mid - 1`。 - 最终,`low`就是最大化的最小距离。 ### 复杂度分析 - **排序**:O(N log N) - **二分查找**:O(log(最大间隔)),每次验证O(N) - 总复杂度:O(N log N + N log(最大间隔)),由于最大间隔最大为10^9,log(10^9)约为30,因此总复杂度约为O(N log N),满足题目要求(N<=100,000)。 ### C++代码实现 ```cpp #include <iostream> #include <algorithm> #include <vector> using namespace std; bool canPlace(vector<int>& positions, int c, int minDist) { int count = 1; // 第一头放在第一个位置 int last = positions[0]; // 上一头的位置 for (int i = 1; i < positions.size(); i++) { if (positions[i] - last >= minDist) { count++; last = positions[i]; if (count >= c) return true; // 提前结束 } } return count >= c; } int main() { int n, c; cin >> n >> c; vector<int> positions(n); for (int i = 0; i < n; i++) { cin >> positions[i]; } sort(positions.begin(), positions.end()); int low = 0; int high = positions.back() - positions.front(); // 最大可能距离 while (low < high) { int mid = low + (high - low + 1) / 2; // 向上取整 if (canPlace(positions, c, mid)) { low = mid; } else { high = mid - 1; } } cout << low << endl; return 0; } ``` ### 代码说明 1. **输入处理**:读取隔间数量`n`、的数量`c`和隔间位置数组`positions`。 2. **排序**:对隔间位置排序,以便贪心放置。 3. **二分查找**: - `low`初始为0,`high`初始为最大坐标与最小坐标的差。 - 使用`mid = low + (high - low + 1)/2`确保向上取整,避免死循环。 4. **验证函数`canPlace`**: - 从第一个隔间开始放置第一头- 遍历后续隔间,如果当前隔间与上一个放置点距离>=`minDist`,则放置一头- 如果放置的数量达到`c`,返回`true`;否则`false`。 5. **输出**:二分结束后的`low`即为最大化的最小距离。 ### 相关问题 1. **如果要求最小距离必须严格大于某个值,算法应如何调整?** 2. **如果隔间不是直线分布而是环形分布,问题该如何解决?** 3. **如何证明贪心策略的正确性?** 4. **如果的数量C=1,算法是否仍然有效?** [^1]: 二分答案的核心是通过单调性将最优化问题转化为判定问题,切割蛋糕问题中边长与块数的单调递减关系是基础。 [^4]: 提前终止条件 `if (sum >= k) return true;` 显著减少不必要的计算,尤其当 $k$ 较小或蛋糕尺寸较大时。</think>### Farmer John 棚分配问题:最大化相邻的最小距离 #### 问题描述 给定 $N$ 个分布在直线上的棚位置 $x_1, x_2, \dots, x_N$($0 \leq x_i \leq 10^9$)和 $C$ 头($2 \leq C \leq N$),需要将分配到棚中,使得**任意两头之间的最小距离尽可能大**。即求最大化的最小间隔距离 $d$,满足存在一种分配方案使得任意两头的距离都不小于 $d$。 #### 算法思路:二分查找 + 贪心策略 1. **二分答案框架**: - 目标:最大化最小距离 $d$ - 单调性:若 $d$ 可行,则所有小于 $d$ 的值都可行;若 $d$ 不可行,则所有大于 $d$ 的值都不可行 - 二分范围:$d \in [0, \max(x_i) - \min(x_i)]$ 2. **贪心验证函数**: -棚位置排序:$x_1 \leq x_2 \leq \dots \leq x_N$ - 贪心放置- 第一头总是放在 $x_1$ - 后续每头放在满足 $x_j \geq \text{上一个位置} + d$ 的最小 $x_j$ 处 - 验证:若能放置至少 $C$ 头,则 $d$ 可行 #### 时间复杂度 - 排序:$O(N \log N)$ - 二分查找:$O(\log (\max_x - \min_x))$ - 验证函数:$O(N)$ - 总复杂度:$O(N \log N + N \log D)$($D$ 为坐标范围) #### C++ 代码实现 ```cpp #include <iostream> #include <algorithm> #include <vector> using namespace std; // 验证函数:检查最小距离d是否能放置C头 bool canPlace(vector<int>& positions, int c, int d) { int count = 1; // 已放置的数量(第一头放在positions[0]) int last_pos = positions[0]; // 上一头的位置 for (int i = 1; i < positions.size(); i++) { if (positions[i] - last_pos >= d) { count++; last_pos = positions[i]; if (count >= c) return true; // 提前终止 } } return count >= c; } int main() { int n, c; cin >> n >> c; vector<int> positions(n); for (int i = 0; i < n; i++) { cin >> positions[i]; } // 关键步骤:对棚位置排序 sort(positions.begin(), positions.end()); // 二分查找框架 int low = 0; int high = positions.back() - positions.front(); // 最大可能距离 while (low < high) { int mid = low + (high - low + 1) / 2; // 向上取整 if (canPlace(positions, c, mid)) { low = mid; // 尝试更大的距离 } else { high = mid - 1; // 减小距离 } } cout << low << endl; return 0; } ``` #### 关键点说明 1. **排序的必要性**: - 棚位置无序,必须先排序才能使用贪心策略 - 排序后保证位置递增:$x_1 \leq x_2 \leq \dots \leq x_N$[^4] 2. **贪心策略正确性**: - 每次选择满足条件的最小位置,最大化剩余空间的利用率 - 该策略能确保找到最优解,因为跳过更近的位置不会获得更好的结果[^4] 3. **二分查找细节**: - 中点计算使用 `mid = low + (high - low + 1)/2` 确保向右收敛 - 终止条件 `low < high` 保证找到最大可行解 - 向上取整避免死循环 4. **验证函数优化**: - 提前终止:当已放置足够数量时立即返回 - 距离计算:只与前一个位置比较,$O(1)$ 时间完成 #### 示例分析 输入: ``` 5 3 1 2 4 8 9 ``` 处理过程: 1. 排序后位置:`[1, 2, 4, 8, 9]` 2. 二分查找: - $d=4$:位置1→4→9(距离3/5),可行 - $d=5$:位置1→8(距离7),但无法放置第三头(9-8=1<5),不可行 3. 输出:`4` #### 相关问题 1. **如果要求最小距离严格大于 $d$,算法应如何调整?** 2. **当棚位置是环形分布时(如圆形牧场),如何解决?** 3. **如何证明贪心策略的最优性?** 4. **如果每头需要占用多个连续棚,问题该如何扩展?** 5. **当棚位置动态变化时,如何设计高效的数据结构?** [^1]: 二分答案的核心是通过单调性将最优化问题转化为判定问题 [^4]: 贪心策略的正确性依赖于有序序列和局部最优选择的全局最优性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值