P2678 跳石头
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 NN 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 MM 块岩石(不能移走起点和终点的岩石)。
输入格式:
第一行包含三个整数 L,N,ML,N,M ,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L \geq 1L≥1 且 N \geq M \geq 0N≥M≥0 。
接下来 NN 行,每行一个整数,第 ii 行的整数 D_i( 0 < D_i < L)Di(0<Di<L) , 表示第 ii 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式:
一个整数,即最短跳跃距离的最大值。
输入样例#1:
25 5 2
2
11
14
17
21
输出样例#1:
4
此题要求最短距离最大,对于最短距离我们知道其范围是1~L,那么可以在该单调区间内进行二分查找不断缩小范围。
那么还需要一个判断函数judge来判断当前距离作为最短距离是否是可行解。如果是可行解,但有可能它不是最优解,那么因为求最大值我们还需要继续向其右部区间查找是否有更优解;如果不是可行解,那么可行解只可能在其左部区间,二分向左部查找。
#include<iostream>
using namespace std;
int l, n, m;
int a[50001];
bool judge(int x) //判断该x距离是否是一个合法解
{
int before = 0;
int num = 0;
for (int i = 1; i <= n+1; i++)
{
if (a[i] - before < x) //如果该距离比最短距离还短,说明该石头需移走,计数器加1
num++;
else
before = a[i];
}
if (num > m) //需移走的石头总数超过了m,说明这个距离是非法解
return false;
return true;
}
int main()
{
int ans;
cin >> l >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
a[n + 1] = l; //注意审题
int left = 1, right = l, mid;
while (left <= right) //基本的二分查找
{
mid = (left + right) / 2;
if (judge(mid)) //合法继续去右边找更大的,非法则去左边找合法的
{
ans = mid;
left = mid + 1;
}
else
right = mid - 1;
}
cout << ans << endl;
return 0;
}
这类题目的核心是找到二分的范围区间进行二分查找,二分查找的条件通过一个judge函数检测,judge函数在不同题中有不同写法,但目的都是来判断一个解是否合法。
P1182 数列分段
题目描述
对于给定的一个长度为N的正整数数列 A-iA−i ,现要将其分成 M(M≤N)M(M≤N) 段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 42451 要分成 3 段
将其如下分段:
[4 2][4 5][1]
第一段和为 6 ,第 2 段和为 9 ,第 3 段和为 1 ,和最大值为 9 。
将其如下分段:
[4][2 4][5 1]
第一段和为 4 ,第 2 段和为 6 ,第 3段和为 6 ,和最大值为 6 。
并且无论如何分段,最大值不会小于 6 。
所以可以得到要将数列 4 2 4 5 1 要分成 3 段,每段和的最大值最小为 6 。
输入输出格式
输入格式:
第 1 行包含两个正整数N,M。
第 2 行包含 NN 个空格隔开的非负整数 A_iAi ,含义如题目所述。
输出格式:
一个正整数,即每段和最大值最小为多少。
输入样例#1:
5 3
4 2 4 5 1
输出样例#1:
6
对于 20% 的数据,有 N≤10;
对于 40% 的数据,有 N≤1000 ;
对于 100% 的数据,有 N≤100000,M≤N,Ai 之和不超过 10^9 。
#include<iostream>
#include<algorithm>
using namespace std;
int n, m; //分m段,则要划出m-1条线
int a[100001];
bool judge(int x) //x是所有段中的最大值,希望其最小
{
int line = 0;
int sum = 0;
for (int i = 1; i <= n; i++)
{
if (sum + a[i] > x) //如果和超过了最大值说明需要将其分段,线段数加1,重新开始计算sum
{
line++;
sum = a[i];
}
else
sum += a[i];
}
if (line >= m) //所加的线段数超过了要求的m-1条,说明这个最大和是非法解
return false;
return true;
}
int main()
{
int sum = 0, ans = 0, max=-1;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
if (a[i] > max)
max = a[i];
sum += a[i];
}
int l = max, r = sum, mid; //注意二分的范围区间!!最小的和是最大的一个数,最大的和是所有数的总和!!
while (l <= r) //开始二分选择,合法则向左找还有没有更优解,不合法则向右找合法解
{
mid = (l + r) / 2;
if (judge(mid))
{
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
cout << ans << endl;
return 0;
}
P1577 切绳子
题目描述
有N条绳子,它们的长度分别为Li。如果从它们中切割出K条长度相同的
绳子,这K条绳子每条最长能有多长?答案保留到小数点后2位。
输入格式:
第一行两个整数N和K,接下来N行,描述了每条绳子的长度Li。
输出格式:
切割后每条绳子的最大长度。
输入样例#1:
4 11 8.02 7.43 4.57 5.39
输出样例#1:
2.00
使用二分法范围寻找绳子长度的最优解,分出的K段绳子长度范围在0~max之间,max是所有绳子长度的最大值。
#include<iostream>
#include<cstdio>
using namespace std;
int n, k, num = 0;
double a[10001], sum = 0;
bool judge(double x) //x是绳子的长度,期望其最大
{
num = 0;
for (int i = 1; i <= n; i++)
num += floor(a[i] / x);
if (x*k > sum||num<k) //如果K条绳子总长超过绳子总长或该长度的绳子无法分出K条,那么说明x过大,需要向左找合法解
return false;
return true;
}
int main()
{
cin >> n >> k;
double max = -1, ans = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sum += a[i];
if (a[i] > max)
max = a[i];
}
double l = 0, r = max+1, mid;
while (l<=r)
{
mid = (l + r) / 2;
if (judge(mid))
{
ans = mid;
l = mid+0.01; //如果合法就向右找是否有更长的绳长作为更优解
}
else
r = mid-0.01;
}
printf("%.2f\n", ans);
return 0;
}
此外这个题还需要注意的是精度问题!