题目
HDU - 6487
http://acm.hdu.edu.cn/showproblem.php?pid=6487
2018黑龙江省赛 H题Overflow
题意
一个桶里有水,给你很多个木块,都丢进去后,水的高度变为多少?
思路
其他人没考虑全面的做法:
没有考虑水量不够的情况,因为这道题的数据很水,也能直接过。例如这个:https://blog.csdn.net/kuronekonano/article/details/82821059#commentsedit
不过如果数据出的好,就过不了了。
我是这样做的:
首先看性质:
木块的密度分两种,大于等于水的,小于水的。
小于水的在水量充足的情况下会漂浮,在水量不够的情况下是沉底的。
密度大于等于水在水量充足的情况下会完全在水里,在水量不够的情况下是会露出来一部分的,所以要关注水量多少。
该做法的主要部分是 木块等价、 底面积后缀和 和 计算 。
先把木桶当做没有水的,然后所有的木块都丢进去,然后再加水。
这里我们把木块等价变换一下。
我们知道,密度比水大的木块,它能整个被淹掉。
而密度比水小的木块,会有一个临界点,就是浮力刚好等于重力的时候,我们算出这个情况下,水淹没了这个木块的高是多少(临界点的高度),将木块的高度更新为这个新的高度(被水淹没的高度)。因为密度比水小,不管你水加的再多,都不可能淹没超过这个高度,这么做就是把这个密度比水小的木块,等价成了一个密度比水大的木块,而这个新木块的高度为 原先密度比水小时 能被水淹没的最大高度(底面的边长不变)。
然后我们就把所有的木块按新高度从小到大排序,(放在结构体里,L是底面边长,nL是高度)。
木块等价后,我们就可以把所有的木块放到桶底,然后把水倒进去。
接下来算了一下木块的底面积后缀和,因为后面按高度分层计算要用到。
然后我们按高度分层计算。
现在看木块的高度最小的和桶底这一层,用 这一层的体积 减去 木块占据的那些部分的体积 后,看手中的水能不能够把剩下的空间填满。
可以的话就往更高一层看,看高度最小的木块和高度第二小的木块这一层。看剩下的水足够把空隙填满?足够就再往后面推(看高度第二小和高度第三小),如果水不够填满的话,说明水位就在当前层,我们拿剩下的水除以(底面积 - 高度后缀),就得出水在这一层的高度h,h再加上那个高度比较小的木块的高度,就得到水位了。
如果水量足够把所有木块都淹没,那么要考虑水是否会溢出水桶,溢出就等于桶的高度,否则就再加上桶顶与最高木块之间这一层的水位高度就好了。
这个做法就线性的跑了几次,时间复杂度为O(n)
整体流程是:
读数据,然后进行木块等价变换,接着按高度排序,再计算一下底面积后缀和,最后分层求解水位即可。
代码
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
double L[maxn];//木块边长
double P[maxn];//密度
double nL[maxn];//等价木块后的高度
double suf[maxn];//底面积后缀和
struct node{
double nL;
double L;
}len[maxn];
int cmp(node a,node b)
{
return a.nL < b.nL;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)//10
{
int n;
scanf("%d",&n);//1e4
for(int i = 1;i <= n;i++)
scanf("%lf %lf",&L[i],&P[i]);
double S,H,V;
scanf("%lf %lf %lf",&S,&H,&V);
for(int i = 1;i <= n;i++)
{
if(P[i] < 1)//把会漂浮起来的木块等价成不会漂浮的木块
nL[i] = P[i]*L[i];//等价成的木块的新的高度,底面边长不变 。其实是等于P*(L的三次方)再除以 L的二次方,化简后就是P*L了
else
nL[i] = L[i];
}
for(int i=1;i<=n;i++)//一开始忘记开结构体了,排序后底边和高对不上号,这里修改的时候图快,就这样改了,这里可以优化。
{
len[i].nL = nL[i];
len[i].L = L[i];
}
sort(len + 1,len + 1 + n,cmp);
suf[n] = len[n].L * len[n].L;//底面积
for(int i = n - 1;i >= 1;i--)
suf[i] = suf[i+1] + len[i].L * len[i].L;//底面积后缀和
double ans = 0;
len[0].nL = 0;
len[0].L = 0;
for(int i = 1;i <= n;i++)
{
if(len[i].nL == len[i-1].nL)//高度相等
continue;
else//高度不相等
{
V -= S * (len[i].nL - len[i-1].nL) - (len[i].nL - len[i-1].nL) * suf[i];
//S * (len[i].nL - len[i-1].nL) - (len[i].nL - len[i-1].nL) * suf[i]为水把这一个高度差里面的空隙用水填满需要的水的体积
if(V < 0)//如果水不够填满这一层除了木块占用部分以外的空间
{
V += S * (len[i].nL - len[i-1].nL) - (len[i].nL - len[i-1].nL) * suf[i];//不够填满,再加回去
ans += V / ( S - suf[i] ) ;//算出这一层的高度,ans加上
V = 0;//水都用完了
break;
}
else//剩余的V>=0,足够填满当前层除了木块占用部分以外的空间
ans = len[i].nL;
}
}
if(V > 0)//如果淹没所有的木块后还剩水,看剩下的水有没有溢出水桶
{
if(V < (H - len[n].nL) * S)
ans += V / S;
else
ans = H;
}
printf("%.2lf\n",ans);
}
return 0;
}