NOIP 2009 道路游戏

Description

给你一个 n 1000 个点的环,要求在上面走过 m 1000 条边,从第 i 个点出发需要花费代价 c o s t i ,每一次出发最多经过 p m 条边,每条边在每个时刻都有一个收益,每个时刻可以从第 i 个点走向第 i + 1 个点,不能不走,中途可以从任意一个点出发,最大化收益 代价。

Solution

f [ i ] 表示前 i 个时段的最大收益值,

s u m [ i ] [ j ] 表示到第 i 个时段为止,前 j 条路段的金币之和,

c o s t [ i ] 表示从第 i 个工厂购买机器人的花费。

考虑 O ( n 3 ) 做法,有

f [ i ] = m a x { f [ i k ] + s u m [ i ] [ j ] s u m [ i k ] [ j k ] c o s t [ j k ] }

由于 s u m [ i ] [ j ] 非负,则

f [ i ] = m a x { f [ i k ] s u m [ i k ] [ j k ] c o s t [ j k ] } + s u m [ i ] [ j ]

发现括号里面的式子整体减 k ,如果用 g [ i ] [ j ] 表示 f [ i ] s u m [ i ] [ j ] c o s t [ j ] ,那么 g [ i k ] [ j k ] 就是与它在一个对角线上的元素。

又由于有 p 的限制,于是可以用 n 个单调队列维护 n 个对角线,如图:

f [ i ] s u m [ i ] [ j ] c o s t [ j ] 即为单调队列的关键字。

Error
  • 一开始把式子写成了 f [ i ] [ j ] = m a x { f [ i k ] [ j k ] + s u m [ i ] [ j ] s u m [ i k ] [ j k ] c o s t [ j k ] }
    这样是不对的,因为上一次出发的起始点可以是任意位置,而不限于 j k

  • 单调队列写错了, t a i l 应该是最后一位 + 1

  • 初始化时,应该把 0 时刻加到所有的队列里,因为 p 的所有时刻都可以由 0 时刻转移过来。

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;
}

猜你喜欢

转载自blog.csdn.net/Milkyyyyy/article/details/82016845
今日推荐