【Code Forces 320E】【三分+字首和】Weakness and Poorness 最大的區間和的絕對值儘可能小
【題意】
給你一個整數數列a1~an
找到一個實數x,使得a1-x,a2-x,…,an-x的weakness儘可能小
一個數列的weakness,是這個數列所有區間poorness的最大值
一個區間的poorness,是這個區間內所有元素和的絕對值
也就是我們想找到一個實數x,使得
a1-x,a2-x,…,an-x內的所有區間段中(C(n,2)+C(n,1)個),
最大的元素的和絕對值儘可能小。
【型別】
三分+字首和
【分析】
使得最大值儘可能小,感覺就是二分(三分)。
第一個問題——
x的改變,答案是否會產生單調性的變化?
首先,所有數都是在-10000~10000之間的,
如果x比-10000還小,x越小肯定答案越糟糕
同理,如果x比10000還大,那麼x越大,答案也會越糟糕。
那,如果x在[min(a[i]),max(a[i])]區間範圍內呢?
和詩詩討論了40分鐘,終於討論出來了,好開心O(∩_∩)O~
我們定義橫座標為x,縱座標為區間內所有數-x之後和的絕對值
於是,對於一個長度為n區間而言,設初始和為sum,在這個座標系上,函式為abs(sum-nx)
就是一條斜率是±n的折線,對於這個折線,我們是可以三分求最低值的。
而現在有多個區間,我們可以把它們都畫在這個座標圖上,
這裡其實有一個結論——多個開口方向相同的二次曲線,我們取max值,得到的函式曲線(也就是這道題關於x的答案曲線),雖然是多段,但依然具有三分性
(對於這道題的模型和座標系,也必定滿足先遞減後遞增)
於是可以三分!
第二個問題——
在x為某個值的情況下,我們怎麼求元素的和絕對值最大的那個的區間段的元素和的絕對值?
對於區間最值,
要不我們可以通過資料結構維護,要不就通過字首和。
資料結構?因為有絕對值,所以感覺不會寫。
字首和?但是這道題有字首和的加減法性質麼?
我們發現——
如果先對每個點求完絕對值,那就沒有加減法性質了,因為我們不知道是相加關係還是相消關係。
然而只是數值而言的話,是滿足字首和性質。於是我們可以維護一個字首和,不涉及到絕對值。
連續區間段的和就是兩個字首和相減。
所以說我們只要記錄在這個字首和之前,最大的字首和還有最小的字首和。
然後就能對應出最大的差值,得到最大的區間和的絕對值。
這樣是可以在O(n)的時間複雜度更新出的。
【時間複雜度&&優化】
O(nlog(20000))
這題還有考慮上凸包或下凸包的O(n)解法,然而我並不會QwQ
【trick】
為了控制精度誤差,不用eps,而用迴圈次數永遠都是不錯的選擇。
【資料】
Input
3 1 2
1 1 1
Output
3
Input
4 2 3
1 2 4 8
Output
79
【程式碼】
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<functional>
#include<string>
#include<algorithm>
#include<time.h>
#include<bitset>
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
using namespace std;
const int N=2e5+10,M=0,Z=1e9+7,maxint=2147483647,ms31=522133279,ms63=1061109567,ms127=2139062143;const double eps=1e-8,PI=acos(-1.0);//.0
map<int,int>mop;
struct A{};
int n;
double a[N],b[N];
double cnt(double x)
{
for(int i=1;i<=n;i++)b[i]=a[i]-x;
double minv=0;//積累字首和的最小值
double maxv=0;//積累字首和的最大值
double sum=0;//積累字首和
double tmp=0;//利用前三項,積累得到最大區間差值
for(int i=1;i<=n;i++)
{
sum+=b[i];
gmax(tmp,sum-minv);
gmax(tmp,maxv-sum);
gmax(maxv,sum);
gmin(minv,sum);
}
return tmp;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)scanf("%lf",&a[i]);
double l=-10000;
double r=10000;
double ans=1e18;
for(int tim=1;tim<=100;tim++)
{
double lm=(l+l+r)/3;
double tmpl=cnt(lm);
double rm=(l+r+r)/3;
double tmpr=cnt(rm);
if(tmpl<=tmpr)r=rm;
else l=lm;
}
printf("%.12f\n",cnt(l));
}
return 0;
}