JZOJ 4249 游戏

这题有一个非常水的贪心做法……
但是显然斜率优化更优美!

暴力的方程 \(f_i = \max\limits_{0 \le j < i} (f_j + (i - j) a_i)\)
对于决策 \(0 \le k < j < i\),假设 \(j\) 优于 \(k\)
\[\begin{align*} f_j + (i - j) a_i & \ge f_k + (i - k) a_i \\ f_j - j \cdot a_i & \ge f_k - k \cdot a_i \\ f_j - f_k & \ge (j - k) a_i \\ \dfrac{f_j - f_k}{j - k} & \ge a_i \end{align*}\]

但是我们发现 \(a_i\) 并没有单调性……所以不能直接单调队列,因为可能会排除掉对将来有用的决策……
考虑换成单调栈,找决策的时候二分即可。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <cstdio>
using namespace std;
const int N = 1e5;
int n,a[N + 5];
int st[N + 5],top;
int f[N + 5];
inline double slope(int x,int y)
{
return (double)(f[x] - f[y]) / (x - y);
}
inline int bs(int x)
{
if(top == 1)
return st[top];
int l = 2,r = top,mid,ans = 1;
while(l <= r)
{
mid = l + r >> 1;
if(slope(st[mid - 1],st[mid]) >= x)
l = mid + 1,ans = mid;
else
r = mid - 1;
}
return ans;
}
int main()
{
freopen("game.in","r",stdin),freopen("game.out","w",stdout);
scanf("%d",&n);
for(register int i = 1;i <= n;++i)
scanf("%d",a + i);
st[top = 1] = 0;
for(register int i = 1;i <= n;++i)
{
int x = bs(a[i]);
f[i] = f[st[x]] + (i - st[x]) * a[i];
for(;top > 1 && slope(st[top - 1],st[top]) < slope(st[top],i);--top);
st[++top] = i;
}
printf("%d\n",f[n]);
}