目录
【蓝桥杯】分巧克力
儿童节那天有 K 位小朋友到小明家做客。
小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。
切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者 2 块 3×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含两个整数 Hi 和 Wi。
输入保证每位小朋友至少能获得一块 1×1 的巧克力。
输出格式
输出切出的正方形巧克力最大可能的边长。
数据范围
1≤N,K≤1e5
1≤Hi,Wi≤1e5
输入样例:
2 10
6 5
5 6
输出样例:
2
思路:
简单的二分,就是有一些小细节需要注意!
思路实现:
1.二分查找
int l = 1, r = 1e5;
//这个二分的边界处理是反过来的, 如果不够分 说明尺寸应该缩小 所以令r = mid - 1
while(l < r)
{
int mid = l + r + 1 >> 1;
if(divide(mid) < k) //如果不够分
r = mid - 1;
else
l = mid;
}
2.模拟分巧克力:
int divide(int x)
{
int sum = 0;
for(int i = 0; i < n; i++)
{
//一定要加括号!!!
//如果不加括号, 那么第二个 w[i] / x 的向下取整会出问题 相当于前三个数计算完之后再除 x
sum += (h[i] / x) * (w[i] / x);
}
return sum;
}
完整代码(C++):
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
int h[N], w[N];
int n;
int divide(int x)
{
int sum = 0;
for(int i = 0; i < n; i++)
{
//一定要加括号!!!
sum += (h[i] / x) * (w[i] / x);
}
return sum;
}
int main()
{
int k;
cin >> n >> k;
for(int i = 0; i < n; i++)
scanf("%d%d", &h[i], &w[i]);
int l = 1, r = 1e5;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(divide(mid) < k) //如果不够分
r = mid - 1;
else
l = mid;
}
cout << l << endl;
return 0;
}
【分巧克力引申题】机器人跳跃问题
机器人正在玩一个古老的基于 DOS 的游戏。
游戏中有 N+1 座建筑——从 0 到 N 编号,从左到右排列。
编号为 0 的建筑高度为 0 个单位,编号为 i 的建筑高度为 H(i) 个单位。
起初,机器人在编号为 0 的建筑处。
每一步,它跳到下一个(右边)建筑。
假设机器人在第 k 个建筑,且它现在的能量值是 E,下一步它将跳到第 k+1 个建筑。
如果 H(k+1)>E,那么机器人就失去 H(k+1)−E 的能量值,否则它将得到 E−H(k+1) 的能量值。
游戏目标是到达第 N 个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是机器人至少以多少能量值开始游戏,才可以保证成功完成游戏?
输入格式
第一行输入整数 N。
第二行是 N 个空格分隔的整数,H(1),H(2),…,H(N)代表建筑物的高度。
输出格式
输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。
数据范围
1≤N,H(i)≤ 1e5
输入样例1:
5
3 4 3 2 4
输出样例1:
4
输入样例2:
3
4 4 4
输出样例2:
4
输入样例3:
3
1 6 4
输出样例3:
3
思路:
先分析能量的得失:
设当前的能量为E,下一层楼高度为H,如果 H > E 那么 E = E - (H - E) ----> E = 2E - H,如果 H <= E 那么 E = E + E - H ----> E = 2E - H。 综上,无论情况如何 E 都将变为 2E - H!
再分析如何确定最小的能量:
首先暴力枚举肯定能过一些case,但是数据大的可能会超时,根据给的数据分析n≤100000 => O(nlog(n)) => 各种sort,线段树、树状数组、set/map、heap、dijkstra+heap、spfa、求凸包、求半平面交、二分。加上这个题的特点,我们可以确定是通过二分查找最小的能量。
思路实现:
1.二分查找
int l = 1, r = 1e5;
while(l < r)
{
int mid = l + r >> 1; // mid为能量
if(dfs(0 ,mid)) //dfs为题目中的check函数 当dfs为true 表明当前能量可以跳到最后
{
r = mid;
}
else
{
l = mid + 1;
}
}
2.模拟机器人跳跃
//使用递归写法代码简洁
bool dfs(int u, int E) //u为当前跳跃次数 E为当前能量
{
//一定要先判断E是否小于0!!
if(E < 0)
return false;
//接下来判断 是否跳跃到最后一个 或者 当前能量是否大于最高的那层楼
if(u >= n || E >= maxv)
return true;
return dfs(u + 1, 2 * E - h[u + 1]);
}
/*
ps: 为什么要先判断E是否小于0 ----> 存在这样一种情况: 当跳到最后一幢楼时 E < 0
如果先判断u >= n则返回值为 true 不符合条件
终止条件 E >= maxv 的确定 因为每次E = 2E - H 当 E >= maxv 时 无论如何E都是大于0了
如果不加这条判断条件 int和long long 定义的 E 最终都会溢出 导致结果异常
*/
完整代码(C++):
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;
int h[N], maxv;
int n;
bool dfs(int u, int E)
{
//一定要先判断E是否小于0!!
if(E < 0)
return false;
if(u >= n || E >= maxv)
return true;
return dfs(u + 1, 2 * E - h[u + 1]);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
scanf("%d", &h[i]);
maxv = max(maxv, h[i]);
}
int l = 1, r = 1e5;
while(l < r)
{
int mid = l + r >> 1;
if(dfs(0 ,mid))
{
r = mid;
}
else
{
l = mid + 1;
}
}
cout << l << endl;
return 0;
}