<题目链接>
题目大意:
给定一段序列,每次进行两次操作,输入1 x代表插入x元素(x元素一定大于等于之前的所有元素),或者输入2,表示输出这个序列的任意子集$s$,使得$max(s)-mean(s)$表示这个集合的最大值与平均值的最大差值。
解题分析:
首先,因为输入的$x$是非递减的,所以要使$max(s)-mean(s)$最大,肯定$max(s)$就是最后一个输入元素的大小。$x$已经确定了,现在就是尽可能的使$mean(s)$尽可能的小。如何使得平均值最小呢?肯定是从最前面的最小的元素开始选,因为最后一个元素是一定要选的(选做最大的元素),所以$mean(s)$函数必然是一个下凹的函数(在开始选小的元素时,平均值会被慢慢的拉低(因为一开始只有一个最大的元素),选到后面比较大的元素时,平均值又会逐渐上升)。所以我们可以用三分寻找max(s)-mean(s)函数的峰值。同时本题也可以用尺取来做,也是用来寻找峰值。
三分:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5e5+5; ll sum[N]; template<typename T> inline void read(T&x){ x=0;int f=1;char c=getchar(); while(c<‘0‘ || c>‘9‘){ if(c==‘-‘)f=-1;c=getchar(); } while(c>=‘0‘ && c<=‘9‘){ x=x*10+c-‘0‘;c=getchar(); } x*=f; } inline double cal(int loc,int x){ return (double)(sum[loc]+x)/(loc+1); } int main(){ int q;read(q); int op,x,cnt=0; while(q--){ read(op); if(op==1){ read(x); sum[++cnt]=sum[cnt-1]+x; }else{ //对前cnt-1个元素进行三分 int l=1,r=cnt-1; while(l<r){ int m1=(l+l+r)/3,m2=(l+r+r)/3; if(cal(m1,x)>=cal(m2,x)){ if(l==m1)break; l=m1; }else{ if(r==m2)break; r=m2; } } double ans=(double)(sum[l]+x)*1.0/(l+1); for(int i=l;i<=r;i++)ans=min(ans,(double)(sum[i]+x)/(i+1)); //因为最后[l,r]之间可能不只有一个元素 printf("%.10lf\n",x-ans); } } }
尺取:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5e5+5; ll sum[N]; template<typename T> inline void read(T&x){ x=0;int f=1;char c=getchar(); while(c<‘0‘ || c>‘9‘){ if(c==‘-‘)f=-1;c=getchar(); } while(c>=‘0‘ && c<=‘9‘){ x=x*10+c-‘0‘;c=getchar(); } x*=f; } inline double cal(int loc,int x){ return (double)(sum[loc]+x)/(loc+1); } int main(){ int q;read(q); int op,x,cnt=0,top=0; while(q--){ read(op); if(op==1){ read(x); sum[++cnt]=sum[cnt-1]+x; }else{ while(top<cnt){ if(cal(top+1,x)<cal(top,x))top++; else break; } printf("%.10lf\n",x-cal(top,x)); } } }
原文地址:https://www.cnblogs.com/00isok/p/10691637.html
时间: 2024-10-04 04:13:44