目录
1.POJ1505 Copying Books
原题链接:1505 -- Copying Bookshttp://poj.org/problem?id=1505
大意:
有n本不同页数的书要分给k个抄写员抄,每本书只能分配给一个抄写员,每个抄写员必须得到一系列连续的书。抄写所有书籍所需的时间由分配到最多工作的抄写员决定。因此,要最小化分配到最多页数的抄写员的页数。
输入输出要求:
如果有多个解决方案,则打印一个最小化分配给第一个抄写员的解决方案,然后再打印给第二个抄写员等。但每个抄写员必须至少分配一本书。
Sample Input
2 9 3 100 200 300 400 500 600 700 800 900 5 4 100 100 100 100 100Sample Output
100 200 300 400 500 / 600 700 / 800 900 100 / 100 / 100 / 100 100
思路分析:
要求最大值中的最小值,就应该考虑使用二分法,按照输出要求,要使越前面的部分书籍书尽可能少,考虑使用贪心,尽量使得后面的部分能合并的话先合并。
二分的初始范围:使用单本书中最大页数作为左端点,所有页数的和作为右端点。这样做的好处:左端点取最大页数,可以避免有抄写员分配到0本书的可能,右端点则考虑到一个抄写员可能分配到的最多任务的情况。
二分函数的设计思路:利用二分求出答案,对于每次二分的答案,计算这个最大页数可以划分给几个抄写员,如果能划分的部分少了,说明范围取大了,需要减小,则r=mid;反之,说明这个页数太少了,需要增大。
计算可以划分为几部分:从后往前遍历数组,使用贪心的思想,每次尽可能使后面的页数能合并的先合并,partSum记录当前部分的页数,如果还没有超过最大值,就再往这个部分添加书,如果这一次添加使得这一部分的总页数超过了最大值,则说明需要在上一次停止划分。那么就当前这一本书作为新部分纳入的第一本书,将partSum的值更新为当前书的页数,并且cnt++,表示新分配出了一个部分。
输出前的处理:
1、在计算可以划分几个部分的时候,每当发现不能再添加书了,利用一个bool型数组记录当前位置,以便在输出时可以在这里打印“/”。
2、需要在每次计算可以划分几个步骤前,还原bool数组以免对下一次记录产生影响。
3、在得到最终的二分结果后,需要确认是否所有抄写员都分配到了工作,如果没有,从前往后遍历数组,在没有被记录可以打印“/”的位置都标记为true,知道所有“/”都已经用完。这样使得前面的部分书籍书尽可能小。
注意点:
1、在求sum时可能会爆long long。(每本书的页数最多为1e7,最多有500本书)。
2、记得还原bool数组。
3、二分得到结果后,检查是否划分出了足够的部分,如果不够,需要处理。
AC代码如下:
//
// Created by LittleMelon on 2022/9/17.
//
//二分法的特点:非常适用于查找最大值中的最小值或找最小值中的最大值
//不是所有的二分题都需要排序,此题因为习惯先写一个sort导致debug了很久找不到问题。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=500+10;
int q[N];
bool p[N];//用来记录part划分的点。
int k,m;
int countPart(ll len){
ll partSum=0;
int cnt=1;
for (int i = 0; i < N; ++i) {
p[i]= false;//每次初始化,防止被上一次影响
}
for (int i = k-1; i >=0; --i) {//贪心思想:每次尽量使后面的可以合并,使得后面的部分更大
partSum+=q[i];
if (partSum>len){
partSum=q[i];
cnt++;
p[i]= true;//标记划分的点,输出时在该位置后加斜杠
}
}
return cnt;
}
ll binarySearch(ll l,ll r){
ll mid;
while (l<r){
mid=l+r>>1;
int part=countPart(mid);
if (part<=m) {
r=mid;
}else{
l=mid+1;
}
}
return l;
}
int main(){
int t;
scanf("%d",&t);
while (t--){
scanf("%lld%lld",&k,&m);
ll sum=0;//划分成部分时,页数最多的情形就是只有一个scriber来copy所有书,此时所有书的页数之和即为答案。用sum来作为二分的最大值。
ll large=0;//由于每个scribe必须被分配至少一本书,因此划分的最小部分的页数,一定大于任何单本书的页数。用large作为二分的最小值。
for (int i = 0; i < k; ++i) {
scanf("%d",&q[i]);
sum+=q[i];
if (q[i]>large)
large=q[i];
}
//二分求最大值中的最小值
ll maxMinus=binarySearch(large,sum);
int cnt= countPart(maxMinus);
//如果用最大值中的最小值从后往前贪心分配后,还可以再分,就从前往后分配。
for (int i = 0; i < k&&cnt<m; ++i) {
if (!p[i]){
p[i]= true;
cnt++;
}
}
printf("%d",q[0]);
for(int i=1;i<k;i++)
{
if(p[i-1])
printf(" /");
printf(" %d",q[i]);
}
printf("\n");
}
return 0;
}
2.HDU1969 Pie
原题链接:
Problem - 1969http://acm.hdu.edu.cn/showproblem.php?pid=1969大意:
有n个馅饼,我和我的F个朋友需要分配到体积一样的馅饼,所有馅饼高度都是1,馅饼都应该大小相等(但不一定形状相同)。求每个人可以分配到的馅饼的最大体积。
输入输出要求:
Sample Input
3
3 3
4 3 3
1 24
5
10 5
1 4 2 3 4 5 6 5 4 2
Sample Output
25.1327
3.1416
50.2655
思路分析:
浮点数二分求结果就好。
注意点:
1、需要将给出的半径转换成面积再去计数,因为馅饼只需要大小相等,不需要形状相同。
2、PI的精度需要取得大一些,不然会WA,或许可以找到更好的表达PI的方式。
AC代码如下:
//
// Created by LittleMelon on 2022/9/18.
//
#include <iostream>
using namespace std;
const int N=1e4+10;
const double PI=3.1415926535898;//这个PI精度起初取小了
int r[N];
double pie[N];
int n,f;
//分配的体积为v时,可以分给几个人。
int countPie(double v){
int cnt=0;
for (int i = 0; i < n; ++i) {
cnt+=(int)(pie[i]/v);
}
return cnt;
}
double binarySearch(double l,double r){
double mid;
while (r-l>1e-7){
mid=(l+r)/2;
if (countPie(mid)<f+1){//注意f+1,因为寿星本人也需要分配到pie
r=mid;//如果可以分出的数量太少了,就将v变小,即r=mid
} else{
l=mid;
}
}
return r;
}
int main(){
int T;
scanf("%d",&T);
while (T--){
scanf("%d%d",&n,&f);
double max=0;
for (int i = 0; i < n; ++i) {
scanf("%d",&r[i]);
pie[i]=r[i]*r[i]*PI;//因为分成的pie只需体积一致,底面积的形状没有要求。需要将题目给出的半径处理成面积再进行计算。
if (pie[i]>max)
max=pie[i];
}
double v=binarySearch(0,max);
printf("%.4f\n",v);
}
return 0;
}
3.HDU4004 The Frog's Game
原题链接:
Problem - 4004http://acm.hdu.edu.cn/showproblem.php?pid=4004大意:
青蛙要过河,河流宽度为L,河流中有n块石头,青蛙只能通过跳在石头上过河,并且限制最多跳m次,求最短的最长距离。
思路分析:
根据之前的经验,我们已经能很快从“求最长的最短距离”联想到二分法。贪心的思想体现在check()函数中,难点也在check()函数的设计中。
check函数:首先,用local表示初始位置,如果当前位置大于河流宽度表示到达对岸,则跳出循环。mid表示跳跃的最长距离,每次循环找到这一次可以跳跃到最远的石头,并且将跳跃次数cnt加1。
二分函数:如果需要跳的次数小于等于m,说明有更短的最长距离,则r=mid;如果大于m说明这个距离太短了,不足以跳到对岸,需要增长l=mid+1;初始左边界采用排序后石子之间的最大距离,以避免落入水中的情况,右边界采用河流的宽度。类似于第一题。
注意点:
1、从石头跳到对岸也算作一次跳跃,要将终点位置加入到数字中。
2、为了使得check函数在跳到对岸后不会继续向前跳跃。应当在对岸位置后面新加一个无穷大的位置。
AC代码:
//
// Created by LittleMelon on 2022/9/18.
//
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e6+10;
const int INF=0x3f3f3f3f;
int ll,n,m;
int stone[N];
//计算最大跳跃距离为mid时,最少需要跳几次。
int check(int mid){
int local=0;
int cnt=0;
int i=0;
while (local<ll) {
local+=mid;
while (stone[i]<=local){
i++;
}
local=stone[i-1];
cnt++;
}
return cnt;
}
int binarySearch(int l,int r){
int mid;
while(l<r){
mid=l+r>>1;
int cnt=check(mid);
if (cnt<=m){
r=mid;
} else{
l=mid+1;
}
}
return l;
}
int main(){
while(~scanf("%d%d%d",&ll,&n,&m)){
for (int i = 0; i < n; ++i) {
scanf("%d",&stone[i]);
}
stone[n]=ll;//由于跳到陆地上也算作一次跳跃,需要将最后的落地点也添加进数组。
stone[n+1]=INF;//将终点后的距离设为无穷大,使得不会继续前进到终点后的位置。
sort(stone,stone+n);
int maxx=0;//不可以放做全局变量
for (int i = 1; i <= n; ++i) {
if (stone[i]-stone[i-1]>maxx){
maxx=stone[i]-stone[i-1];
}
}
int dis=binarySearch(maxx,ll);//将相邻石头间的最大距离作为二分的起始左边界,使得二分时不会必然落入河中的情况。河的总长度作为右边界。
printf("%d\n",dis);
}
return 0;
}
4.POJ3258 RiverHopscotch
原题链接:
3258 -- River Hopscotchhttp://poj.org/problem?id=3258大意:
奶牛要过河,河流宽度为L,河流中有n块石头,奶牛只能通过跳在石头上过河,可以拿掉m个石头,求最长的最短距离。
思路分析:
有了之前的经验,显然也是考虑贪心+二分。
check函数:len表示二分所得的最短距离,while循环遍历数组,如果目前的石头距离当前位置的距离大于最短距离,则可以跳在这块石头上,如果不行就要拿掉所以cnt++,cnt记录需要拿掉的石头数。
二分函数:如果需要拿掉的石头过多,说明最短距离过大了,r=mid-1;如果需要拿掉的石头小于等于m,说明可能有更大的最短距离,l=mid。因为是l=mid,又是int类型,在除以2时,会舍弃小数点后的内容,所以在求mid时,如果l只比r小1,那么此时会造成死循环,因此需要用l+r+1除以2来求mid。
注意点:
与上一题一样。
AC代码:
//
// Created by LittleMelon on 2022/9/19.
//
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int rock[N];
int L,n,m;
int check(int len){
int local=0;
int i=0;
int cnt=0;
while (local<L) {
if (rock[i]-local>=len){
local=rock[i];
} else{
cnt++;
}
i++;
}
return cnt;
}
int binarySearch(){
int l=0,r=L;
int mid;
while (l<r){
mid=l+r+1>>1;
if (check(mid)<=m){
l=mid;
} else{
r=mid-1;
}
}
return l;
}
int main(){
scanf("%d%d%d",&L,&n,&m);
for (int i = 0; i < n; ++i) {
scanf("%d",&rock[i]);
}
rock[n]=L;
rock[n+1]=INF;
sort(rock,rock+n);
cout<<binarySearch();
return 0;
}
5. POJ3104 Drying
原题链接:
3104 -- Dryinghttp://poj.org/problem?id=3104大意:
Jane想在短时间内晒干n件衣服,衣服自然干燥时,每分钟可以减少1的水量,烘干机一次可以烘一件衣服,烘一分钟可以减少k的水量。求最少的干燥时间。
思路分析:
最开始想到的是用贪心和vector暴力解,每过一分钟就排序,让烘干机去烘水量最多的衣服,如果烘干了一件,就erase掉。但是很快就发现,erase掉的过程会影响排序和最后一件衣服,并不可行。
看了网上的思路,主要是找到一个计算公式,显然比暴力要巧妙。函数的来源是设当前衣服需要使用t分钟的烘干机,总时间是用二分求的mid,那么这件衣服的水量cloth[i]<=t*k+mid-t。衣服的烘干时间表示用烘干机的除水量t*k和自然风干的除水量mid-t;
注意点:
1、由于不到k的水量使用烘干机也算1分钟,使用ceil()函数向上取整。
2、n的范围是1e5,k的范围是1e9,最后的答案在l和r之间,一定不会爆longlong,但是求总时间的t,在加的过程中可能有数据会爆longlong
AC代码:
//
// Created by LittleMelon on 2022/9/19.
//
#include <iostream>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int cloth[N];
int n, k;
ll getTime(ll mid) {
ll t = 0;
for (int i = 0; i < n; ++i) {
if (cloth[i] > mid) {
t += ceil((cloth[i] - mid) * 1.0 / (k - 1));
}
}
return t;
}
int main() {
scanf("%d", &n);
int max = 0;
for (int i = 0; i < n; ++i) {
scanf("%d", &cloth[i]);
if (cloth[i] > max)
max = cloth[i];
}
scanf("%d", &k);
if (k <= 1) {
printf("%d\n", max);
return 0;
}
int l = 1, r = max;
int mid;
while (l < r) {
mid = l + r >> 1;
if (getTime(mid) <= mid) {
r = mid;
} else {
l = mid + 1;
}
}
printf("%d\n", l);
return 0;
}