题意:
给一个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\n",ans);
}
return 0;
}