Description |
给你一个 个点的环,要求在上面走过 条边,从第 个点出发需要花费代价 ,每一次出发最多经过 条边,每条边在每个时刻都有一个收益,每个时刻可以从第 个点走向第 个点,不能不走,中途可以从任意一个点出发,最大化收益 代价。
Solution |
设 表示前 个时段的最大收益值,
表示到第 个时段为止,前 条路段的金币之和,
表示从第 个工厂购买机器人的花费。
考虑 做法,有
由于 非负,则
发现括号里面的式子整体减 ,如果用 表示 ,那么 就是与它在一个对角线上的元素。
又由于有 的限制,于是可以用 个单调队列维护 个对角线,如图:
即为单调队列的关键字。
Error |
一开始把式子写成了
这样是不对的,因为上一次出发的起始点可以是任意位置,而不限于 。单调队列写错了, 应该是最后一位 。
初始化时,应该把 时刻加到所有的队列里,因为 的所有时刻都可以由 时刻转移过来。
Code |
#include <cstdio>
#include <cstring>
const int N = 1005, INF = 0x7fffffff;
int a[N][N], sum[N][N], cost[N], f[N], id[N][N], q[N][N], h[N], t[N];
int read() {
int x = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x;
}
int max(int x, int y) {
if (x >= y) return x; return y;
}
int main() {
int n = read(), m = read(), p = read();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) a[i%n][j] = read();
for (int i = 0; i < n; ++i) cost[i] = read();
for (int i = 1; i <= m; ++i) //前缀和
for (int j = 0; j < n; ++j)
sum[i][j] = sum[i-1][(j-1+n)%n] + a[j][i];
for (int i = 0; i < n; ++i) id[1][i] = i + 1; //单调队列标号
for (int i = 2; i <= m; ++i)
for (int j = 0; j < n; ++j) id[i][j] = id[i-1][(j-1+n)%n];
for (int i = 1; i <= m; ++i) f[i] = -INF; //初始化
for (int i = 1; i <= n; ++i) q[i][t[i]++] = 0; //切勿忘记0时刻的存在
for (int i = 1; i <= m; ++i) {
for (int j = 0; j < n; ++j) {
int x = id[i][j];
while (h[x] < t[x] && i - q[x][h[x]] > p) ++h[x];
int k = i - q[x][h[x]];
f[i] = max(f[i], f[i-k] + sum[i][j] - sum[i-k][(j-k+n)%n] - cost[(j-k+n)%n]);
}
for (int j = 0; j < n; ++j) {
int x = id[i][j];
int tmp = f[i] - sum[i][j] - cost[j];
while (h[x] < t[x]) {
int k = i - q[x][t[x]-1];
if (f[i-k] - sum[i-k][(j-k+n)%n] - cost[(j-k+n)%n] <= tmp) --t[x];
else break;
}
q[x][t[x]++] = i;
}
}
printf("%d\n", f[m]);
return 0;
}