Codeforces Round 618 (Div. 2)
E. Water Balance
题意:
一排有 n n n 个水箱,其中 i i i 个水箱装有 a i a_i ai 升水。水箱从左到右依次编号为 1 1 1 至 n n n 。
您可以执行以下操作:选择某个子段 [ l , r ] [l, r] [l,r] ( 1 ≤ l ≤ r ≤ n 1\le l \le r \le n 1≤l≤r≤n )( 1 ≤ l ≤ r ≤ n 1\le l \le r \le n 1≤l≤r≤n ),重新均匀分配水箱 l , l + 1 , … , r l, l+1, \dots, r l,l+1,…,r 中的水。换句话说,用 a l + a l + 1 + ⋯ + a r r − l + 1 \frac{a_l + a_{l+1} + \dots + a_r}{r-l+1} r−l+1al+al+1+⋯+ar 替换每个 a l , a l + 1 , … , a r a_l, a_{l+1}, \dots, a_r al,al+1,…,ar 。例如,如果选择 [ 1 , 3 , 6 , 7 ] [1, 3, 6, 7] [1,3,6,7] 中的 l = 2 , r = 3 l = 2, r = 3 l=2,r=3 ,新的水量将是 [ 1 , 4.5 , 4.5 , 7 ] [1, 4.5, 4.5, 7] [1,4.5,4.5,7] 。您可以多次此操作。
你能得到的最小水量的字典序是多少?
请注意
序列 a a a 在词法上比相同长度的序列 b b b 小,当且仅当以下条件成立时:在 a a a 和 b b b 不同的第一个(最左边)位置,序列 a a a 中的元素比 b b b 中的相应元素小。
思路:
因为是字典序最小,所以在所有序列中第一个数最小的就是最小序列,然后才看第二个,第三个等等。因此首要任务就是如何找到这个最小的第一个数。
假设我们已经决定好了将前 i i i 个数进行均摊,现在要加入第 i + 1 i+1 i+1 个数,当然希望这个数比前面数的均值要小,这样就可以进一步拉低前面数的均值。如果第 i + 1 i+1 i+1 个数大于前面的均值的话,如果后面还有足够小的数,也是有可能拉低均值的。如果满足这个情况的话,假如说第 j j j 个数是那个小数,那么区间 [ i + 1 , j ] [i+1,j] [i+1,j] 的均值应该要小于 [ 1 , i ] [1,i] [1,i],而在此之前,区间 [ i + 1 , j − 1 ] [i+1,j-1] [i+1,j−1] 的均值一定小于第 j j j 个数。
也就是说,如果下一个数比前面的整体的均值要小,那么就一定可以合并到前面的整体中去,如果导致这个整体的均值小于了前面的均值,就再把这个整体合并到前面的整体。而如果下一个数比前面的整体大,就暂时放到后面观察情况。这个思路很像单调栈。
code:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
int n,a[maxn];
deque<pair<ll,ll> > q;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
ll tot=a[i],len=1;
while(!q.empty() && q.back().second*tot<=q.back().first*len){
tot+=q.back().first;
len+=q.back().second;
q.pop_back();
}
q.push_back(make_pair(tot,len));
}
while(!q.empty()){
for(int i=1;i<=q.front().second;i++)
printf("%.9lf\n",1.*q.front().first/q.front().second);
q.pop_front();
}
return 0;
}