经典二分穷举

经典二分穷举

对二分的理解:

最后的答案一定在可确定的lowhigh之间,在题目中确定lowhigh即可!

然后由mid=low+high/2  来确认是否符合题意来确定最后的答案。

  

解决令最小。。。最大或最大。。。最小  等问题

POJ3273

题意:

n个数分为m组,每组的天数必须是连续的,尽量让最大的一组数之和最小,最后输出最大的一组的和。

 

代码:

//Memory Time 

//612K   297MS 

 

#include<iostream>

using namespace std;

 

int n; //天数

int m; //规定的分组数

 

/*判断用当前的mid值能把天数n分成几组*/

/*通过比较groupm的大小,对mid值进行优化*/

 

bool judge_group(int mid,int money[])

{

int sum=0;

int group=1;    //当前mid值能把n天分成的组数(初始把全部天数作为1)

 

for(int i=1;i<=n;i++)  //从第一天开始向下遍历每天的花费

if(sum+money[i]<=mid)  //当前i天之和<=mid时,把他们归并到一组

sum+=money[i];

else               //若 前i-1天之和 加上第i天的花费 大于mid

{

sum=money[i];  //则把前i-1天作为一组,第i天作为下一组的第一天

group++;    //此时划分的组数+1

}

 

if(group>m)

return false;   //若利用mid值划分的组数比规定的组数要多,则说明mid值偏小

else

return true;    //否则mid值偏大

}

 

int main(void)

{

while(cin>>n>>m)

{

int* money=new int[n+1];  //每天花费的金额

int low=0;  //下界

int high=0; //上界

 

for(int i=1;i<=n;i++)

{

cin>>money[i];

 

high+=money[i];   //把所有天数的总花费作为上界high(相当于把n天都分作1组)

if(low<money[i])

low=money[i]; //n天中花费最多的那一天的花费作为下界low(相当于把n天分为n组)

}                     //那么要求的值必然在[low,high]范围内

 

int mid=(low+high)/2;

 

while(low<high)  //可能在low==high之前,分组数就已经=m,但是mid并不是最优,因此要继续二分

{

if(!judge_group(mid,money))

low=mid+1;     //mid值偏小,下界前移

else

high=mid-1;    //mid值偏大,上界后移

 

mid=(low+high)/2;

}

 

cout<<mid<<endl; //二分搜索最后得到的mid值必然是使得分组符合要求的最优值,需要好好理解一下!!!

 

delete money;

}

return 0;

}

 

华丽的分割线================================================华丽的分割线

 

POJ3258

题意:

一条河长度为L,开始和终点分别有一块石头,中间有m块石头,可以移走n块,使得最小的石头间距最大

 

代码:

仍是二分查找问题,跟上一道题思路很像,也是分组问题,需要好好理解一下二分思想。

//Memory Time 

//420K   391MS 

 

#include<iostream>

#include<algorithm>

using namespace std;

 

int main(void)

{

int L;  //河总长

int n;  //河中石头数(除起点S和终点外E

int m;  //移除石头数

 

while(cin>>L>>n>>m)

{

/*Input & Initial*/

 

int* dist=new int[n+2];  //i块石头到起点石头的距离为dist[i]

dist[0]=0;    //起点S

dist[n+1]=L;  //终点E

 

int low=L;   //上界(一次跳跃的最短距离)

int high=L;   //下界(一次跳跃的最大距离)

for(int i=1;i<=n+1;i++)

{

if(i<=n)   //仅输入1~n,当i=n+1时仅用于寻找low

cin>>dist[i];

 

if(low > dist[i]-dist[i-1])

low=dist[i]-dist[i-1];

}

 

sort(dist,dist+(n+2));   //根据石头到S的距离升序排列

 

/*Binary-Search*/

while(low<=high)

{

int mid=(low+high)/2;  //对最大跳和最小跳的距离折中,二分查找mid相对于最优解是偏大还是偏小

                       //假设mid是移除m个石头后的最短距离

 

int delrock=0;    //利用当前的mid值能移除的石头数

int sum=0;   //类比POJ 3273, 这里是 连续距离的累加值

             //当在第i个距离累加后sum

 

for(int i=1;i<=n+1;)

{

if( (sum+=dist[i]-dist[i-1]) <= mid)

{

i++;

delrock++;

}

else   //当从第i个距离累加到i+k个距离后,若sum>mid,则k个距离作为一段

{

i++;

sum=0;  //sum0,从第i+k+1个距离重新累加

}

}

 

if(delrock<=m)   //本题难点之一:即使delrock==m也不一定找到了最优解

low=mid+1;   //用当前mid值移除的石头数小于规定数,说明mid偏小

else             

high=mid-1;  //反之mid偏大

}

 

/*Output & Relax*/

 

cout<<low<<endl;

 

delete dist;

}

 

return 0;

}

华丽的分割线================================================华丽的分割线

 

POJ 3122

好开心!!!终于自己打出来一到二分题,虽然挺水的,不过也是有收获的!!

 

Pi 最好位数长一点,比如3.14159265359

还有控制精度大一点,比如保留三位就 1e-5 四位就1e-6

 

pie问题

题意:

npie分给f+1个人,每个人分到的pie必须只来自一个pie,并且每个人分到的pie是一样大的,保留三位小数

 

代码:

 

#include<cstdio>

#define pi 3.14159265359

#define eps 1e-5

double a[10050];

double low, high;

int n, f;

double mid;

bool judge(double mid)

{

int pie = 0;

 

for(int i = 0; i < n;i++)

{

if(a[i]/mid > 0)

{

pie+=a[i]/mid; 

}

}

 

if(pie >= f + 1)  //pie可以分给这些人,有可能比较小

return 1;

else return 0;//pie不够分,说明太大

}

 

int  main()

{

int t;

scanf("%d", &t);

 

while(t--)

{

scanf("%d%d", &n, &f);

double sum = 0;

 

for(int i = 0; i < n; i++)

{

int tmp;

scanf("%d", &tmp);

a[i] = tmp * tmp * pi;

sum += a[i];

}

 

low = 0;

high = sum / (f + 1);

while(high - low > eps)

{

 

mid = (low + high) / 2;

 

if(judge(mid))

{

low = mid;

}

else

{

high = mid;

}

}

 

printf("%.4f\n", mid);

}

 

return 0;

}

华丽的分割线================================================华丽的分割线

 

POJ1905

 

 

 

题意:

不重要,自己再搜,主要学到一个公式,角度弧度公式  θr = s(θ为度数,r为半径,s为度数对应的弧长),主要是这道题还要倒方程!

 

代码:

//Memory Time 

//244K   0MS 

 

#include<iostream>

#include<math.h>

#include<iomanip>

using namespace std;

 

const double esp=1e-5;   //最低精度限制

 

int main(void)

{

double L,n,c,s;   //L:杆长 ,n:温度改变度 , c:热力系数  ,s:延展后的杆长(弧长)

double h;    //延展后的杆中心 到 延展前杆中心的距离

double r;   //s所在圆的半径

 

while(cin>>L>>n>>c)

{

if(L<0 && n<0 && c<0)

break;

 

double low=0.0;    //下界

double high=0.5*L; //  0 <= h < 1/2L   (1/2L并不是h的最小上界,这里做一个范围扩展是为了方便处理数据)

 

double mid;

s=(1+n*c)*L;

while(high-low>esp)  //由于都是double,不能用low<high,否则会陷入死循环 

{                    //必须限制lowhigh的精度差

mid=(low+high)/2;

r=(4*mid*mid+L*L)/(8*mid);

 

if( 2*r*asin(L/(2*r)) < s )     //h偏小

low=mid;

else       //h偏大

high=mid;

}

h=mid;

 

cout<<fixed<<setprecision(3)<<h<<endl;

}

return 0;

}

 

 

华丽的分割线================================================华丽的分割线

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值