派-网址:http://noi.openjudge.cn/ch0111/05/
我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。
我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。
请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。
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
#include
#include
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>i;
}
改进:都乘以10000 放大到小数点后4位
----------------------
只能过3个点
//#include
#include
#include
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=5) left = left/10 +1;
else left = left/10;
cout<>i;
}
测试如下代码:
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
#include
#include
#include
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< 这正是枚举搜索,计算机傻瓜式工作的强项 3、本题坑点:有人说关于pi=3.1415926 精度不够会错5个点 所以pi=3.141592653589793 或者写出pi=cos(-1){math.h好像} 格式化输出,四舍五入,对于这种实数题目就不要渴望 while(left 计算机里面的 0.0000000000000009 其实也是0 , While(right-left>1e-5) 基本就对了 如果3.14153基本等于3.14152 有人说while(right-left>1e-5)写成while(right-left>1e-8) 会超时,没测试 无论怎么着,感觉,思路简单,细节不少 --------------------------------------------------------------- 其中多有不当之处,请批评指正