AT2289 [ARC067D] Yakiniku Restaurants(水题)
题目大意
有编号从 $ 1 $ 到 $ N $ 的 $ N $ 家烧烤店,烧烤店在一条线上按照编号顺序排序,第 $ i $ 家烧烤店与第 $ i + 1 $ 家烧烤店的距离是 $ A_i $ 。
你有编号从 $ 1 $ 到 $ M $ 的 $ M $ 张烧烤券,不管是在哪一家烧烤店都可用烧烤券来吃烧烤,在第 $ i $ 家烧烤店用烧烤券 $ j $ 可以吃到一顿美味度为 $ B_{i,j} $ 的烧烤,每一张烧烤券只能使用一次,但是在一家烧烤店你可以使用任意多张烧烤券。
你想从自己选择的一家烧烤店开始(随意选择一个开始),然后不断地用未使用的烧烤券去另一家烧烤店。你最终的幸福值是所吃所有烧烤的美味度减去所走的总路程。求最大可能的最终幸福值( $ M $ 张券必须用完)。
数据范围
输入的数字都是整数
$ 2 leq N leq 5 imes 10^3 $
$ 1 leq M leq 200 $
$ 1 leq A_i leq 10^9 $
$ 1 leq B_{i,j} leq 10^9 $
解题思路
容易发现选择的肯定是一条线段而且两端必选。
所以我们从右边开始枚举左端点,维护一个答案数组 (tans[j]) 表示目前左端点固定,右端点在 j 的最优答案,接下来操作就是左端点向左移动一格,并更新答案扫一遍。
如何更新答案,单独考虑每张票,只可能是从某个位置买换到左端点买,这里要求原来的某个位置获利比这个位置小,所以我们可以用一个单调栈来维护前缀最大值,然后差分前缀和即可。
const int N = 5005, M = 205;
ll d[N], b[M][N], ans, n, m;
int st[M][N], tp[M]; ll t[N], tans[N];
inline void add(int l, int r, ll k) { t[l] += k, t[r+1] -= k; }
int main() {
// freopen ("hs.in","r",stdin);
read(n), read(m);
for (int i = 1;i < n; i++) read(d[i]);
for (int i = 1;i <= n; i++)
for (int j = 1;j <= m; j++)
read(b[j][i]);
for (int i = 1;i <= m; i++)
st[i][tp[i] = 1] = n + 1, b[i][n + 1] = 1e15;
for (int i = n;i >= 1; i--) {
for (int j = 1;j <= m; j++) {
ll *B = b[j]; int *St = st[j], &T = tp[j]; add(i, i, B[i]);
while (B[St[T]] <= B[i]) add(St[T], St[T-1]-1, B[i] - B[St[T]]), T--;
St[++T] = i;
}
add(i + 1, n, -d[i]);
ll res = 0;
for (int j = i;j <= n; j++)
res += t[j], t[j] = 0, tans[j] += res, Mx(ans, tans[j]);
}
write(ans);
return 0;
}