首页 > 代码库 > hdu 4923 Room and Moor (单调栈+思维)

hdu 4923 Room and Moor (单调栈+思维)

题意:

给一个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;
}