Luogu5155 [USACO18DEC]Balance Beam
Description
有一个点在一根长度为\(n\)的数轴上,它初始在刻度\(k\)处。任意时刻你都可以作出连两个决策,
- 移动,\(\frac{1}{2}\)的概率往左移一个单位,\(\frac{1}{2}\)的概率往右移一个单位
- 结束,你可以获得当前点上的权值\(f_i\)为收益后结束游戏
另外,当你走到位置\(0\)或\(n + 1\)时,你会立即结束游戏,并且无法获得任何收益
如果你的决策均为最优决策,那么对于任意一点\(k \in [1, n]\)期望收益为多少
Solution
首先有这样一个性质,一个点在一个长度为\(L\)的线段位置\(x\)上,它会在每次等概率地往两边走,一旦走到线段的端点处便停止。那么这个点走到左端点的概率是\(\frac{L - x}{L}\),走到右端点的概率是\(\frac{x}{L}\)
下面是证明
我们可以设\(f(x)\)表示当前点到右端点结束的概率,则\(f(0) = 0, f(L) = 1\)
因为
\[f(i) = \frac{f(i - 1)}{2} + \frac{f(i + 1)}{2}
\]
所以我们可以通过观察发现\(f_i\)就是等差数列的形式(当然你也可以去解方程)
我们发现一旦我们走到一个点,继续移动的期望收益不大于当前点的期望收益我们显然会直接选择结束
我们可以设这两个点为\(a, b\),于是我们便只会在这两个点之间游走,一旦到达其中一个点便会立即结束游戏
如果我们用这样策略,那么获得收益的期望为
\[w_i = \frac{f_b \cdot (i - a)}{b - a} + \frac{f_a \cdot (b - i)}{b - a}
\]
我们可以变形一下
\[w_i = \frac{f_b - f_a}{b - a} \cdot i + \frac{f_a \cdot b - f_b \cdot a}{b - a}
\]
这样就变为了一个一次函数的形式
然后我们把每一个位置对应到一个二维坐标系中去,其中第\(i\)个位置对应\((i, f_i)\)。那么如果位置\(i\)的结束点是\(a, b\),那么它的收益变为线段\(ab\)与直线\(x = i\)交点的纵坐标
我们发现所有与直线\(x = i\)相交的线段中,交点纵坐标的最大的线段一定是位于这\(n + 2\)个点的凸包上的
所以我们只需要求一遍凸包便可以求得所有点的最大收益了
时间复杂度\(\mathcal{O}(n \log n)\)
Code
#include <bits/stdc++.h>
using namespace std;
#define fst first
#define snd second
#define mp make_pair
#define squ(x) ((LL)(x) * (x))
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long LL;
typedef pair<int, int> pii;
template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
inline int read() {
int sum = 0, fg = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1;
for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
return fg * sum;
}
const int maxn = 1e5 + 10;
pii operator - (const pii &a, const pii &b) { return mp(a.fst - b.fst, a.snd - b.snd); }
LL operator * (const pii &a, const pii &b) { return (LL) a.fst * b.snd - (LL) a.snd * b.fst; }
inline bool cmp(const pii &a, const pii &b) {
if (a * b == 0) return a.fst > b.fst;
return a * b < 0;
}
pii A[maxn];
int main() {
freopen("balance.in", "r", stdin);
freopen("balance.out", "w", stdout);
int n = read();
for (int i = 1; i <= n; i++) A[i] = mp(i, read());
A[n + 1] = mp(n + 1, 0);
sort(A + 1, A + n + 2, cmp);
static int S[maxn], top = 0;
S[++top] = 0, S[++top] = 1;
for (int i = 2; i <= n + 1; i++) {
while (top > 1) {
if ((A[S[top]] - A[S[top - 1]]) * (A[i] - A[S[top]]) <= 0) break;
--top;
}
S[++top] = i;
}
for (int i = 2; i <= top && A[S[i - 1]].fst != n + 1; i++) {
int lst = A[S[i - 1]].fst, now = A[S[i]].fst;
for (int j = lst + 1; j <= now; j++)
if (j <= n) printf("%lld\n", ((LL) A[S[i]].snd * (j - lst) * (LL)1e5 + (LL) A[S[i - 1]].snd * (now - j) * (LL)1e5) / (now - lst));
}
return 0;
}