题意:有一个容量为(L)的水库,每天晚上可以放任意体积的水。每天早上会有一定温度和体积的水流入水库,且要保证流入水之后水的总体积不能超过(L)。令体积分别为(V_1,V_2),温度分别为(t_1,t_2)的水混合后的温度为(frac {V_1 * t_1 + V_2 * t_2} {V_1 + V_2})。初始水库为空。现给出(n)天流入水的体积和温度,分别最大化每一天中午水库满容量时的水温。
(n <= 500000, space L,t <= 10^9,space V <= L)
显然记录水温时直接记录温度和体积的乘积,这样混合时直接相加就可以了。这个混合满足结合律。
那么,我们可以把水表示为二维平面上的向量,(x)坐标为体积,(y)坐标为温度和体积的乘积。那么,温度就等于它的斜率。我们贪心一下,如果新加入的水导致水温下降,那么一定会在新加入水后再排水。否则,便可以在新加入水之前排水。这启发我们维护一个下凸包。如果某一天晚上是可以排水的,那么就不先与后面的水混合。否则便把两个向量相加。具体排水时放出斜率较小的水,流入水时不断与现有的水合并直到斜率大于之前的水为止。最大化第(i+1)天水的温度,就是最大化第(i)天放水后的水温,故我们直接从第(i)天的决策更新到第(i+1)天的决策,还可以保证新的决策时刻满足水的体积不超过(L)。
时间复杂度(O(n))。
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef double db;
const db eps = 1e-8;
inline int judge(db x) {
return x > -eps ? x > eps ? 1 : 0 : -1;
}
struct point {
db x,y;
point(db x_=0,db y_=0): x(x_), y(y_) {}
db operator * (const point& a) const {
return x * a.y - y * a.x;
}
point operator + (const point& a) const {
return point(x + a.x, y + a.y);
}
point operator - (const point& a) const {
return point(x - a.x, y - a.y);
}
void operator += (const point& a) {
*this = *this + a;
}
void operator -= (const point& a) {
*this = *this - a;
}
} q[N];
int n,cap,t,v,l=1,r;
point cur,tmp;
int main() {
scanf("%d%d",&n,&cap);
scanf("%d%d",&t,&v);
printf("%d.00000000
",t);
q[++r] = point(v,1.0 * t * v);
cur += point(v,1.0 * t * v);
for (int i = 2 ; i <= n ; ++ i) {
scanf("%d%d",&t,&v);
db a = v;
while (judge(a - q[l].x) >= 0)
a -= q[l].x, cur -= q[l++];
cur -= q[l];
q[l].y *= (q[l].x - a) / q[l].x;
q[l].x -= a;
cur += q[l];
tmp = point(v,1.0 * t * v);
while (l <= r && judge(q[r] * tmp) <= 0)
tmp += q[r], cur -= q[r--];
if (judge(tmp.x - cap) > 0) {
tmp.y *= cap / tmp.x;
tmp.x = cap;
}
q[++r] = tmp;
cur += tmp;
printf("%.8lf
",cur.y / cur.x);
}
return 0;
}
小结:在贪心的基础上维护下凸包(或单调队列),从而容易转移。感觉自己做起来还有难度。