[BZOJ]5483: [Usaco2018 Dec]Balance Beam 乱搞

Description

Bessie为了存钱给她的牛棚新建一间隔间,开始在当地的马戏团里表演,通过在平衡木上小心地来回走动来展示她卓越的平衡能力。Bessie能够通过表演赚到的钱取决于她最终成功跳下平衡木的位置。平衡木上从左向右的位置记为0,1,…,N+1。如果Bessie到达了位置0或是N+1,她就会从平衡木的一端掉下去,遗憾地得不到报酬。如果Bessie处在一个给定的位置k,她可以进行下面两项中的任意一项:

  1. 投掷一枚硬币。如果背面朝上,她前往位置k-1,如果正面朝上,她前往位置k+1(也就是说,每种可能性1/2的概率)。
  2. 跳下平衡木,获得f(k)的报酬(1≤f(k)≤10^9)。
    Bessie意识到她并不能保证结果能够得到某一特定数量的报酬,这是由于她的移动是由随机的掷硬币结果控制。然而,基于她的起始位置,她想要求出当她进行一系列最优的决定之后,她能够得到的期望报酬(“最优”指的是这些决定能够带来最高可能的期望报酬)。例如,如果她的策略能够使她以1/2的概率获得10的报酬,1/4的概率获得8的报酬,1/4的概率获得0的报酬,那么她的期望报酬为加权平均值10(1/2)+8(1/4)+0(1/4)=7

Solution

根据题意列出方程(一开始读入为 a a a,最后答案为 f f f): f i = max ⁡ ( a i , 1 2 ( f i − 1 + f i + 1 ) ) f_i=\max(a_i,{1\over 2}(f_{i-1}+f_{i+1})) fi=max(ai,21(fi1+fi+1))。然而这个方程会互相影响,而且是取 max ⁡ \max max,没法解方程。
所以想到这样的暴力:先把所有 f f f初始化为 a a a,然后暴力不断调整 f f f。这样要调整很多次,显然不行。观察需要调整的条件: f i &lt; 1 2 ( f i − 1 + f i + 1 ) f_i&lt;{1\over 2}(f_{i-1}+f_{i+1}) fi<21(fi1+fi+1),即 f i − f i − 1 &lt; f i + 1 − f i f_i-f_{i-1}&lt;f_{i+1}-f_i fifi1<fi+1fi,然后发现,就是差分后前面比后面要小!设 g i = f i − f i − 1 g_i=f_i-f_{i-1} gi=fifi1,观察调整后的 g g g,我们又发现, g i = g i + 1 = g_i=g_{i+1}= gi=gi+1=原来的 g i g_i gi g i + 1 g_{i+1} gi+1的平均数!然后就可以搞了,每次变平均数相当于把两段合并为一段,而且最多只会合并 n − 1 n-1 n1次,用个堆维护就可以了。
注意最后算答案的时候要避免一切精度误差。

Code

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=100010;
const int inf=2147483647;
const double eps=1e-8;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
int n,st,ed;LL g[Maxn],a[Maxn],b[Maxn];double f[Maxn];
struct Node{double v,sum;int l,r,pre,nxt;}p[Maxn];
double V[Maxn];
struct P{double v;int id;P(double _v,int _id){v=_v,id=_id;}};
bool operator<(P a,P b)
{
    if(abs(a.v-b.v)>=eps)return a.v>b.v;
    return a.id>b.id;
}
bool operator==(P a,P b){return(a.id==b.id&&abs(a.v-b.v)<eps);}
priority_queue<P>q,q0;
void merge(int x,int y)
{
    p[x].v=(p[x].sum+p[y].sum)/((double)p[y].r-p[x].l+1);
    p[x].r=p[y].r;p[x].sum+=p[y].sum;
    q0.push(P(V[y],y));
    if(ed==y)ed=x,p[x].nxt=-1,V[x]=inf;
    else p[x].nxt=p[y].nxt,p[p[x].nxt].pre=x,V[x]=p[x].v-p[p[x].nxt].v;
    q.push(P(V[x],x));
    if(p[x].pre!=-1)
    {
        q0.push(P(V[p[x].pre],p[x].pre));
        V[p[x].pre]=p[p[x].pre].v-p[x].v;
        q.push(P(V[p[x].pre],p[x].pre));
    }
}
int main()
{
    n=read();f[0]=f[n+1]=a[0]=a[n+1]=b[0]=b[n+1]=0;
    for(int i=1;i<=n;i++)f[i]=b[i]=read();
    for(int i=n+1;i;i--)f[i]-=f[i-1],p[i].sum=p[i].v=f[i],p[i].l=p[i].r=i,p[i].pre=i-1,p[i].nxt=i+1;
    for(int i=1;i<=n+1;i++)a[i]=a[i-1]+f[i];
    p[1].pre=p[n+1].nxt=-1;st=1,ed=n+1;
    for(int i=1;i<=n;i++)
    {
        V[i]=p[i].v-p[i+1].v;
        q.push(P(V[i],i));
    }
    V[n+1]=inf;q.push(P(V[n+1],n+1));
    int lst=-1;
    while(1)
    {
        while(!q0.empty()&&q.top()==q0.top())q.pop(),q0.pop();
        P t=q.top();q.pop();
        if(t.v>eps)break;
        merge(t.id,p[t.id].nxt);
    }
    g[0]=g[n+1]=0;
    int x=st;
    while(x!=-1)
    {
        g[p[x].r]=100000LL*b[p[x].r];
        for(int i=p[x].r-1;i>=p[x].l;i--)
        {
            g[i]=g[p[x].r]-(100000LL*(a[p[x].r]-a[p[x].l-1])*((LL)p[x].r-i))/((LL)p[x].r-p[x].l+1);
            if(a[p[x].r]>a[p[x].l-1])g[i]-=(((100000LL*(a[p[x].r]-a[p[x].l-1])*((LL)p[x].r-i))%((LL)p[x].r-p[x].l+1))!=0);
        }
        x=p[x].nxt;
    }
    for(int i=1;i<=n;i++)printf("%lld\n",g[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值