您的位置:首页 > 运维架构

派-详解-noi.openjudge.cn-二分答案

2016-10-11 20:19 435 查看
派-网址:http://noi.openjudge.cn/ch0111/05/

总时间限制: 1000ms 内存限制: 65536kB
描述

我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。

请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。

输入第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。

第二行包含N个1到10000之间的整数,表示每个派的半径。
输出输出每个人能得到的最大的派的体积,精确到小数点后三位。
样例输入
3 3
4 3 3


样例输出
25.133


一、题目分析

最终分割的是体积

假如体积数据为  1 1 90  分3份

显然最优为 30 30 30

首先本身就有3份数据,所有第3大的1 可以肯定分下去 每个人分 1 1 1

然后对数据排序 第2大 + 第1大/2+第1大/2 可以是一种分法 2 45 45 ->2 2 2

第1大/3 + 第1大/3 + 第1大/3 可以是 30 30 30

那假如数据为 1 34 90 呢

分法1:1 1 1

分法2: 34 45 45-> 34 34 34

分法3:30 30 30->30 30 30

那如果是 1 70 90 呢

分法1: 1 1 1

分法2: 70  45 45 -> 45 45 45

分法3:30 30 30

是不是意味着:

如果n份派分成k份的所有情况是:在n>=k情况下

分法1:第k大 第k大 第。。。k大 共k个

分法2:第k-1大 第k-2大  ... 第3大 第1大/2 第2大/2

分法i: 第k-(i-1)大 第k-(i-2)大。。。第i+1大 紧着i个: 第1大/i 第1大/i 第1大/i

???????

对于 1 1 90 90

分3份:1 1 1 1----1 90 45 45-----90 30 30 30----23 23 23 23

但是还有一种 45 45 45 45 显然不只第1大可以分割 第2大也可以

也就是每次分割最大值情况:

1 1 90 45 45 重排序后:1 1 45 45 90

再分割:1 1 45 45 45 45

在分割:1 1 22.5 22.5 45 45 45 

但没有这种分割更优: 1 1 90 30 30 30

再分割 1 1 30 30 30 45 45和1 1 22.5 22.5 45 45 45比起来,再取4份的时候更优

那分6份呢 显然 1 1 30 30 30 30 30 30 最优 

---------------------------------------------

按照如上策略,贪心找分割规律解法失败,尝试去枚举结果,代入后找满足条件的值,

结果保留3位小数,可以很明确找到最大值如51.2364565,那么结果肯定在

0.000 0.001 0.002 。。。51.236 里面出现步长0.001

干脆给体积都乘以1000

0 1 2 ... 51236 找其中满足均分派的结果 比如 

样例

28.2743334 28.2743334 50.2654816处理成

28274 28274 50265

那么结果肯定在 0-50265中间

比如 每个人拿到体积4    28274/4 + 28274/4 + 50265/4  > 3+1 也就是说人均4 肯定可以被分出来,然后遍历找其中最大的

但是2重循环下来题目时间复杂度为 O(n2*1000) 10^4*10^4*10^3 = 10^11次方超时

显然可以二分答案了,但是这个思路的第一个问题来了见如下代码    

---------------------------------------------

只能过2个点

//#include <StdAfx.h>
#include <stdlib.h>
#include <iostream>

using namespace std;
#define PI 3.141592653589793

int n,f;
unsigned int tj[10001],a[10001];

int IsOK(int res)
{
int i,sum=0;
for (i=1;i<=n;i++)
{
sum += tj[i] / res;
}
if (sum>=f) return 1;
else return 0;
}

int main()
{
int i,max=0,left,right;
int ans;
//读取数据
cin>>n>>f;
f++;

for (i=1; i<=n; i++)
{
cin>>a[i];
tj[i] = a[i]*a[i]*1000*PI;
if (tj[i]>max) max=tj[i];
}
//二分遍历查找
left=0;
right=max;
while(left<right)
{
int mid = (left+right)/2;

if (IsOK(mid) ) ans=mid,left=mid+1;
else right=mid-1;
}

if (IsOK(left)) ans=left;

cout<<double(left/1000.0);

//cin>>i;

}


后来分析是在 left=25132 right=25134 mid=25133 并不符合切割规律因为去掉了小数点后的位数,25133是四舍五入来的

改进:都乘以10000 放大到小数点后4位

----------------------

只能过3个点

//#include <StdAfx.h>
#include <stdlib.h>
#include <iostream>

using namespace std;
#define PI 3.141592653589793

int n,f;
unsigned int tj[10001],a[10001];

int IsOK(int res)
{
int i,sum=0;
for (i=1;i<=n;i++)
{
sum += tj[i] / res;
}
if (sum>=f) return 1;
else return 0;
}

int main()
{
int i,max=0,left,right;
int ans;
//读取数据
cin>>n>>f;
f++;

for (i=1; i<=n; i++)
{
cin>>a[i];
tj[i] = a[i]*a[i]*10000*PI;
if (tj[i]>max) max=tj[i];
}
//二分遍历查找
left=0;
right=max;
while(left+10<right)
{
int mid = (left+right)/2;

if (IsOK((mid/10)*10)) left=mid;
else right=mid;
}
if (left%10>=5) left = left/10 +1;
else left = left/10;
cout<<double(left/1000.0);

//cin>>i;

}


后面无论如何改都不行了,后来经看别人代码发现都是用的printf("%.3f")输出,

测试如下代码:

printf("%.1f", 3.45);

printf("%.1f", 3.445);

printf("%.1f", 3.5);

printf("%.1f", 3.55);

printf("%.3f", 3.44445);

printf("%.3f", 3.44455);

printf("%.3f",
3.44545);

printf("%.3f",
3.4454);

printf("%.3f",
3.4455);

我对c的格式化输出,程序的四舍五入彻底没了一个正确的推论,最后抛弃*10000这种放大策略,对实数

老老实实按照实数计算

-----------------------------------

AC代码

/#include <StdAfx.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>

using namespace std;
#define PI 3.141592653589793

int n,f;
unsigned int a[10001];
double tj[10001];

int IsOK(double res)
{
int i,sum=0;
for (i=1;i<=n;i++)
{
sum += int(tj[i] / res);
}
if (sum>=f) return 1;
else return 0;
}

int main()
{
int i;
double max,left,right;
double ans;
//读取数据
cin>>n>>f;
f++;
max=0;
for (i=1; i<=n; i++)
{
cin>>a[i];
tj[i] = a[i]*a[i]*PI;
if (tj[i]>max) max=tj[i];
}
//二分遍历查找
left=0;
right=max;
while(right-left>1e-5)
{
double mid = (left+right)/2;

if (IsOK(mid) ) left=mid;
else right=mid;
}

if (IsOK(left)) ans=left;

printf("%.3f",ans);

//cin>>i;

}
---------------------------------

总结:1、其实这也是在解空间里面深搜枚举,只不过恰好有个符合特征的二分答案策略

        2、在求解方程式 1+x=2+5 我们可以从上往下:x=2+5-1=6

            也可以知道x为整数[0,10000]里面遍历枚举看哪个满足 if (x+1==2+5) {cout<<x;return;}

            这正是枚举搜索,计算机傻瓜式工作的强项

       3、本题坑点:有人说关于pi=3.1415926 精度不够会错5个点 所以pi=3.141592653589793 或者写出pi=cos(-1){math.h好像}

                         格式化输出,四舍五入,对于这种实数题目就不要渴望 while(left<right)中间差值为1,实数的比较本来就是一个精度为题

       计算机里面的 0.0000000000000009 其实也是0 ,

                          While(right-left>1e-5) 基本就对了         如果3.14153基本等于3.14152 

                          有人说while(right-left>1e-5)写成while(right-left>1e-8) 会超时,没测试

       无论怎么着,感觉,思路简单,细节不少

---------------------------------------------------------------

其中多有不当之处,请批评指正

                         

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  二分答案 算法 noip