题意:
给一个0和1组成的序列a,要构造一个相同长度的序列b。b要满足非严格单调,且
值为0到1的实数。最后使得 sum((ai-bi)^2)最小。
算法:
首先a序列開始的连续0和末尾的连续1是能够不考虑的。
由于仅仅要b序列相应开头为0、
末尾为1,既不影响单调性又能使相应的(ai-bi)^2=0。
然后,
先找111100、11100、10这样以1開始以0结束的序列块。每一块相应的b值相等且均为
这一块的平均值,即1的个数/0和1的总个数。
可是要满足b的单调性,则我们用栈来维护,假设后面一块的均值<前面一块的均值,则
合并这两块。也就是仅仅要栈顶的块的均值小于要压入栈的块的均值,就一直合并,直到
满足单调性。再把当前的值压入栈。
最后仅仅要一块块统计相应的sum((ai-bi)^2)就是答案了。
逗逼记忆---->比赛的时候我没有想到块,仅仅想到除去前面的0和后面的1。中间的值都是一样。
用二分或者三分解决,仅仅要控制精度=。
=
P.S: 这题必须注意细节,否则极易丢失精度。主要是我代码凝视的两个我開始没有控制好的地方。
o(╯□╰)o
#include<cstdio> #include<iostream> #include<cstring> #define maxn 100010 using namespace std; struct node { double num,v; //v表示1的个数,num表示0和1的总个数 }; node stk[maxn]; double sum[maxn],a[maxn]; int main() { int T,n; scanf("%d",&T); while(T--) { scanf("%d",&n); memset(sum,0,sizeof(sum)); for(int i=1;i<=n;i++) { scanf("%lf",&a[i]); sum[i] = sum[i-1]+a[i]; } int i = 1,k = n; while(a[i]==0) i++; while(a[k]==1) k--; int top = 0,le = i; node x,y; for(;i<=k;i++) { if(a[i]==0) { while(a[i]==0 && i<=k) //这里假设不加控制i<=k,i可能超出k i++; double a = sum[i-1]-sum[le-1]; double b = (double)i-le; while(top>0 && a/b<stk[top].v/stk[top].num) //这里也不能忘了top>0 { y = stk[top--]; a += y.v; b += y.num; } x.v = a; x.num = b; stk[++top] = x; le = i; } } double ans = 0; while(top) { y = stk[top--]; double val = y.v/y.num; ans += (1-val)*(1-val)*y.v + val*val*(y.num-y.v); } printf("%.6lf ",ans); } return 0; }