1. 程式人生 > >題解-USACO18DEC Balance Beam詳細證明

題解-USACO18DEC Balance Beam詳細證明

Problem

洛谷5155

題意概要:給定一個長為\(n\)的序列,可以選擇以\(\frac 12\)的概率進行左右移動,也可以結束並得到當前位置上的收益,求從每個位置開始時使用最優策略的最大期望收益是多少

\(n\leq 10^5\)

Solution

(翻了翻其他的題解,覺得它們沒講清楚為什麼一定選擇兩個點作為決策結束點)

我相信很多通過了這道題的人都沒有弄清楚這個策略的正確性

關鍵在於需要考慮當前是選擇移動還是直接結束。一個很明瞭的觀點:如果當前移動後的收益期望比當前位置的收益大,那麼會選擇移動;否則選擇直接停止。直接停止的貢獻已經知道,那麼要求的就是當前點選擇移動操作後的收益期望


有一個結論:在長度為\(L\)的數軸上的位置\(x\)處,每次進行左右移動(左右概率都為\(\frac 12\)),若到達\(0\)\(L\)即停止,則到達\(0\)停止的概率為\(\frac {L-x}L\),到達\(L\)停止的概率為\(\frac xL\)

關於這個結論的證明,考慮設在 \(i\) 開始,到\(L\)停止的概率為\(f_i\),由題可得\(f_i=\frac {f_{i-1}+f_{i+1}}2\),不難發現這個方程是等差數列的描述,又由\(f_0=0,f_L=1\)可得\(f_i=\frac xL\),即可得到上面的結論

這個結論的用處後面再說


假設我們已知一些節點移動的期望收益比當前點停止的收益低,即如果進行隨機遊走,一旦到達這些點,一個極端聰明的人是絕不會繼續移動的,設這些點為停止點

會發現,如果從 \(i\) 出發進行移動,那麼移動的期望收益一定是由 \(i\) 往前數第一個停止點和往後數第一個停止點貢獻的

不妨設為 \(a\)\(b\),由我們之前的結論可以得到到達 \(a\) 停止的概率為\(\frac {b-i}{b-a}\),到達 \(b\) 停止的概率為\(\frac {i-a}{b-a}\),由期望公式可得從 \(i\) 出發進行隨機移動的期望收益為 \(E=v_a\cdot \frac {b-i}{b-a}+v_b\cdot \frac {i-a}{b-a}\),比劃一下會發現這是滿足物理裡的槓桿模型的

現在我們得到了一個優秀的做法,就是對於每個點,找到其前後的第一個停止點,得到其移動的期望收益然後與自己的停止收益取最大值


但如何求出所有的停止點呢?

進一步,畫個圖可知,設 \(a\) 座標為 \((a,v_a)\)\(b\) 座標為 \((b,v_b)\),則從 \(i\) 出發進行移動的期望收益是 \(a,b\) 所連線段與直線\(x=i\)的交點縱座標,所有的停止點會滿足在\(x=i\)時,\(v_i\)要比這條線段要高

不難發現若將所有停止點 \((i,v_i)\)畫出來一定成為一個凸包,反證法證明:若存在一個停止點在凸包內,則這個點的移動期望比停止期望大,也就是說這並不是一個停止點,與假設相悖,得證

所以為了得到所有的停止點,只要對所有的\((i,v_i)\)求個凸包即可

證明真有趣

Code

#include <bits/stdc++.h>
typedef long long ll;

inline void read(ll&x){
    char c11=getchar();x=0;while(!isdigit(c11))c11=getchar();
    while(isdigit(c11))x=x*10+c11-'0',c11=getchar();
}

const int N=101000,bs=1e5;
int n,tp;

struct vec{
    int x;ll y;
    inline vec(){}
    inline vec(const int&X,const ll&Y):x(X),y(Y){}
    friend inline vec operator - (const vec&A,const vec&B) {return vec(A.x-B.x,A.y-B.y);}
    friend inline ll operator * (const vec&A,const vec&B) {return A.x*B.y-A.y*B.x;}
}p,st[N];

void push(vec p){
    while(tp&&(p-st[tp])*(st[tp]-st[tp-1])<=0)--tp;
    st[++tp]=p;
}

int main(){
    scanf("%d",&n);vec p;
    for(int i=1;i<=n;++i)p.x=i,read(p.y),p.y*=bs,push(p);
    push(vec(n+1,0));
    for(int i=1,j=0;i<=n;++i){
        while(j<tp&&st[j].x<i)++j;
        if(st[j].x==i)printf("%lld\n",st[j].y);
        else printf("%lld\n",((st[j].x-i)*st[j-1].y+(i-st[j-1].x)*st[j].y)/(st[j].x-st[j-1].x));
    }return 0;
}