根据题意不难发现这个模型是不好进行贪心的,于是可以考虑使用 (dp)。可以令 (dp_i) 表示在 (i) 位置以最优策略能获得的报酬期望值,那么会有转移:
不难发现上面这个 (dp) 的转移是有后效性的,但类似地往两边的转移方式在 [六省联考2017]分手是祝愿 中遇到过。但可以发现的是,在那道题的第一个方法当中因为我们终点都是同一个位置所以才可以改变状态向同一边转移,在本题当中这种方法显然是不可取的。
那么另一种方法能否使用呢?你会发现这样一个事实,我们找到 (i) 左侧第一个停下来更优的位置记作 (l),右侧第一个停下来更优的位置记作 (r)。那么 ([l, r]) 中的位置向左向右都只会走到 (l, r) 然后停止,于是我们可以使用第二种方法来观察一下这一段转移的特色。
首先会有:
那么一般地,会有:
同时需要注意到 (dp_r = f_r),则会有:
类似于前面的归纳方式,同理可以得到:
貌似这样还是看不出有什么特别之处,那么可以尝试将假设进一步特殊化。就令 (l = 0, r = n) 尝试一下,那么会发现:
这不恰好是一个正比例函数的形式吗?即 (y = dfrac{f_n}{n} imes i) 的形式。那么是不是最上面哪个也会是某个一次函数呢?可以考虑能否将 (dp) 转移方程化简成一次函数的形式,不难发现可以整理成如下形式:
可以看出这就是将直线 (y = dfrac{f_r - f_l}{r - l}x + f_l) 向右平移 (l) 个单位所得直线,也就是 ((l, f_l), (r, f_r)) 所连成的直线。那么我们惊喜地发现,以 (l, r) 为停下左右端点对中间每个点 (i) 的贡献为 (i) 在 ((l, f_l), (r, f_r)) 上 (i) 位置对应的纵坐标。那么结合图像可以发现为了使得答案最优,不会存在任意一个点 (i) 其 (f_i) 大于在其在直线上的纵坐标。回过头来整体地看,这所有的直线连成的一个图形不恰好是 ((i, f_i)) 所形成的凸包吗?于是我们只需求出这 (n) 个点的凸包然后在上面求答案即可。因为 (f_i > 0) 因此只需使用类似斜率优化的方法求凸包即可。时间复杂度 (O(n))。
因为本题卡精度,所以只能使用和 (std) 一样的输出方式才能通过 TAT。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 100000 + 5;
int n, top, f[N], st[N], ans[N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
double slope(int a, int b) {
return 1.0 * (f[a] - f[b]) / (a - b);
}
signed main(){
n = read();
rep(i, 1, n) f[i] = read();
rep(i, 0, n + 1) {
for (; top > 1 && slope(st[top - 1], i) > slope(st[top - 1], st[top]); --top) ;
st[++top] = i;
}
rep(i, 1, top - 1) {
int j = st[i];
for(; j <= st[i + 1]; ++j)
ans[j] = 100000ll * (f[st[i]] * (st[i + 1] - j) + f[st[i + 1]] * (j - st[i])) / (st[i + 1] - st[i]);
}
rep(i, 1, n) printf("%lld
", ans[i]);
return 0;
}
值得一提的是,这种放宽条件特殊化的方法在本题当中反复出现。极端情况找规律这种思想也扮演着不可或缺的角色。