【洛谷5155】[USACO18DEC] Balance Beam P(期望+凸壳)
- 给定一个长度为\(n\)的序列\(a\)。
- 当你处于某个位置\(i\)的时候,你可以选择结束游戏并获得\(a_i\)的报酬,也可以不结束游戏随机走向\(i-1\)或\(i+1\),如果走到\(0\)或\(n+1\)则只能结束游戏且无法获得任何报酬。
- 对于每个起始位置,求出最优策略下的期望报酬。
- \(n\le10^5\)
序列游走问题
感觉我之前绝对接触过类似的套路,但翻了翻博客没找到。。。
设\(f_i\)表示从\(i\)出发的最大期望报酬,转移方程:
\[f_i=\max\{\frac{f_{i-1}+f_{i+1}}2,a_i\}
\]
这个方程不但成环,还有个\(\max\),显然无法用一般\(DP\)套路或是高斯消元之类的常规期望算法解决。
但由于\(f_i\)只有两种取值,考虑一个区间\([j,k]\),假设\(f_j=a_j,f_k=a_k\),且\(\forall i\in(j,k)\),\(f_i=\frac{f_{i-1}+f_{i+1}}2\)。
对于\(f_i=\frac{f_{i-1}+f_{i+1}}2\),我们把\(2\)乘到左边并移项变形,得到:
\[f_i-f_{i-1}=f_{i+1}-f_i
\]
也就是说,\([j,k]\)中的\(f_i\)是一个等差数列!
凸壳
我们把所有\((i,a_i)\)看成二维平面上一个点,则我们相当于要从左向右选择若干关键点,并在相邻关键点之间连边(包括\((0,0)\)和\((n+1,0)\)),然后\(f_i\)就等于直线\(x=i\)与所连边交点的纵坐标。
考虑怎么选最优,于是发现最优的选法就是选出一个凸壳(比较显然),直接单调栈维护一下就好了。
代码:\(O(n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,S[N+5];struct P
{
LL x,y;I P(Con LL& a=0,Con LL& b=0):x(a),y(b){}
I P operator - (Con P& o) Con {return P(x-o.x,y-o.y);}
I LL operator ^ (Con P& o) Con {return x*o.y-y*o.x;}
}p[N+5];
int main()
{
RI i,j,x;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&x),p[i]=P(i,(LL)1e5*x);//把(i,a[i])看作一个点
RI T=1;for(p[n+1]=P(n+1,0),i=1;i<=n+1;S[++T]=i++) W(T&&((p[i]-p[S[T]])^(p[S[T]]-p[S[T-1]]))<0) --T;//单调栈求凸壳
for(i=j=1;i<=n;++i) S[j]<i&&++j,printf("%lld\n",S[j]==i?p[i].y:(p[S[j]].y*(i-S[j-1])+p[S[j-1]].y*(S[j]-i))/(S[j]-S[j-1]));//输出交点纵坐标
return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒