[USACO13DEC] Optimal Milking G

[USACO13DEC] Optimal Milking G

题目描述

Farmer John has recently purchased a new barn containing N milking machines (1 <= N <= 40,000), conveniently numbered 1..N and arranged in a row.

Milking machine i is capable of extracting M(i) units of milk per day (1 <= M(i) <= 100,000).  Unfortunately, the machines were installed so close together that if a machine i is in use on a particular day, its two neighboring machines cannot be used that day (endpoint machines have only one neighbor, of course).  Farmer John is free to select different subsets of machines to operate on different days.

Farmer John is interested in computing the maximum amount of milk he can extract over a series of D days (1 <= D <= 50,000).  At the beginning of each day, he has enough time to perform maintenance on one selected milking machine i, thereby changing its daily milk output M(i) from that day forward. Given a list of these daily modifications, please tell Farmer John how much milk he can produce over D days (note that this number might not fit into a 32-bit integer).

FJ 最近买了 1 个新仓库,内含 N 个挤奶机,1 到 N 编号并排成一行。

挤奶机 i 每天能产出 M(i) 单位的奶。不幸的是,机器装得太近以至于如果一台机器 i 在某天被使用, 那与它相邻的两台机器那一天不能被使用(当然, 两端点处的机器分别只有一个与之相邻的机器)。

FJ 可自由选择不同的机器在不同的日子工作。

FJ 感兴趣于计算在 D 天内他能产出奶的最大值。在每天开始时, 他有足够的时间维护一个选中的挤奶机i, 从而改变它从那天起的每日产奶量 M(i)。

给出这些每日的修改,请告诉 FJ 他 D 天中能产多少奶。

输入格式

* Line 1: The values of N and D.

* Lines 2..1+N: Line i+1 contains the initial value of M(i).

* Lines 2+N..1+N+D: Line 1+N+d contains two integers i and m, indicating that Farmer John updates the value of M(i) to m at the beginning of day d.

输出格式

* Line 1: The maximum total amount of milk FJ can produce over D days.

样例 #1

样例输入 #1

5 3 
1 
2 
3 
4 
5 
5 2 
2 7 
1 10

样例输出 #1

32

提示

There are 5 machines, with initial milk outputs 1,2,3,4,5.  On day 1, machine 5 is updated to output 2 unit of milk, and so on.

On day one, the optimal amount of milk is 2+4 = 6 (also achievable as 1+3+2). On day two, the optimal amount is 7+4 = 11. On day three, the optimal amount is 10+3+2=15.

题意简述:给定 n 个点排成一排,每个点有一个点权,多次改变某个点的点权并将最大点独立集计入答案,输出最终的答案。

 

解题思路

  正解是用线段树,对于维护区间 $[l,r]$ 的线段树节点,我们维护以下 $4$ 个信息:

  1. $s_{00}$:不选左端点和不选右端点(即 $a_{l+1} \sim a_{r-1}$)的合法方案的最大值。
  2. $s_{01}$:不选左端点和必选右端点(即 $a_{l+1} \sim a_{r}$)的合法方案的最大值。
  3. $s_{10}$:必选左端点和不选右端点(即 $a_{l} \sim a_{r-1}$)的合法方案的最大值。
  4. $s_{11}$:必选左端点和必选右端点(即 $a_{l} \sim a_{r}$)的合法方案的最大值。

  假设当前节点为 $u$,其左右儿子分别为 $l$ 和 $r$,考虑如何更新 $u$ 的信息(即 pushup 操作)。对于 $u.s_{ij}$,则可以通过 $l.s_{i,x} + r.s_{y,j}$ 更新得到,其中要保证 $x$ 和 $y$ 同时最多只有一个 $1$,意味着只可能有这 $3$ 种情况,$(x,y) = \left\{ (0,0), (0,1), (1,0) \right\}$。因此就有

\begin{cases}
u.s_{00} = \max\left\{ l.s_{00} + r.s_{00}, \; l.s_{00} + r.s_{10}, \; l.s_{01} + r.s_{00} \right\} \\
u.s_{01} = \max\left\{ l.s_{00} + r.s_{01}, \; l.s_{00} + r.s_{11}, \; l.s_{01} + r.s_{01} \right\} \\
u.s_{10} = \max\left\{ l.s_{10} + r.s_{00}, \; l.s_{10} + r.s_{10}, \; l.s_{11} + r.s_{00} \right\} \\
u.s_{11} = \max\left\{ l.s_{10} + r.s_{01}, \; l.s_{10} + r.s_{11}, \; l.s_{11} + r.s_{01} \right\} \\
\end{cases}

  最后因为只查询整个区间的最大值,因此答案就是 max({tr[1].s00, tr[1].s01, tr[1].s10, tr[1].s11}),最优方案必然包括在这 $4$ 种情况内。

  AC 代码如下,时间复杂度为 $O(m \log{n})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 4e4 + 5;

int a[N];
struct Node {
    int l, r;
    LL s00, s01, s10, s11;
}tr[N * 4];

void pushup(int u) {
    tr[u].s00 = max({tr[u << 1].s00 + tr[u << 1 | 1].s00, tr[u << 1].s00 + tr[u << 1 | 1].s10, tr[u << 1].s01 + tr[u << 1 | 1].s00});
    tr[u].s01 = max({tr[u << 1].s00 + tr[u << 1 | 1].s01, tr[u << 1].s00 + tr[u << 1 | 1].s11, tr[u << 1].s01 + tr[u << 1 | 1].s01});
    tr[u].s10 = max({tr[u << 1].s10 + tr[u << 1 | 1].s00, tr[u << 1].s10 + tr[u << 1 | 1].s10, tr[u << 1].s11 + tr[u << 1 | 1].s00});
    tr[u].s11 = max({tr[u << 1].s10 + tr[u << 1 | 1].s01, tr[u << 1].s10 + tr[u << 1 | 1].s11, tr[u << 1].s11 + tr[u << 1 | 1].s01});
}

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        tr[u].s00 = tr[u].s01 = tr[u].s10 = 0;
        tr[u].s11 = a[l];
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int x, int c) {
    if (tr[u].l == tr[u].r) {
        tr[u].s11 = c;
    }
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, c);
        else modify(u << 1 | 1, x, c);
        pushup(u);
    }
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
    }
    build(1, 1, n);
    LL ret = 0;
    while (m--) {
        int x, y;
        scanf("%d %d", &x, &y);
        modify(1, x, y);
        ret += max({tr[1].s00, tr[1].s01, tr[1].s10, tr[1].s11});
    }
    printf("%lld", ret);
    
    return 0;
}

  再提供一个 DDP 的做法。

  定义状态 $f(i,0/1)$ 表示考虑前 $i$ 个数且第 $i$ 个数选(对应 $1$)或不选(对应 $0$)的所有合法选择方案中的最大值,容易知道状态转移方程为$$\displaylines{\begin{cases} f(i,0) = \max\left\{ f(i-1,0), \, f(i-1,1) \right\} \\ f(i,1) = f(i-1,0) + a_i \end{cases}}$$

  注意到状态转移方程中出现的运算符有 $\max$ 和 $+$,将状态转移方程进一步改写成$$\displaylines{\begin{cases} f(i,0) = \max\left\{ f(i-1,0) + 0, \, f(i-1,1) + 0 \right\} \\ f(i,1) = \max\left\{ f(i-1,0) + a_i, \, -\infty + 0 \right\} \end{cases}}$$

  参考 min 与 + 运算转换成类似于矩阵乘法的推导过程,用类似于矩阵乘法的形式来表示状态转移方程,有$$\begin{bmatrix} f(i-1,0) & f(i-1,1) \end{bmatrix} \otimes \begin{bmatrix} 0 & a_i \\ 0 & -\infty \end{bmatrix} = \begin{bmatrix} f(i,0) & f(i,1) \end{bmatrix}$$

  这里定义 $\otimes$ 运算符的操作为 $\left(A_{n \times p} \otimes B_{p \times m}\right)_{i,j} = \max\limits_{1 \leq k \leq p}\left\{ A_{i,k} + B_{k,j} \right\}$。

  为此我们对每个节点开一个矩阵 $\begin{bmatrix} 0 & a_i \\ 0 & -\infty \end{bmatrix}$,然后用线段树去维护区间内矩阵进行 $\otimes$ 运算后的结果。

  最后由于查询的是 $\max\left\{ f(n, 0), f(n,1) \right\}$,假设区间 $[1,n]$ 中的矩阵进行 $\otimes$ 运算后的结果为 $M$,那么答案就是 $\max\{ F_0 \otimes M \}$,其中 $F_0 = \begin{bmatrix} 0 & 0 \end{bmatrix}$。

  AC 代码如下,时间复杂度为 $O(m \log{n})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 4e4 + 5;
const LL INF = -0x3f3f3f3f3f3f3f3f;

int a[N];
struct Matrix {
    array<array<LL, 2>, 2> a;
    
    Matrix(array<array<LL, 2>, 2> b = {0}) {
        a = b;
    }
    auto& operator[](int x) {
        return a[x];
    }
    Matrix operator*(Matrix b) {
        Matrix c({INF, INF, INF, INF});
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < 2; k++) {
                    c[i][j] = max(c[i][j], a[i][k] + b[k][j]);
                }
            }
        }
        return c;
    }
};
struct Node {
    int l, r;
    Matrix f;
}tr[N * 4];

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        tr[u].f[0][1] = a[l];
        tr[u].f[1][1] = INF;
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        tr[u].f = tr[u << 1].f * tr[u << 1 | 1].f;
    }
}

void modify(int u, int x, int c) {
    if (tr[u].l == tr[u].r) {
        tr[u].f[0][1] = c;
    }
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, c);
        else modify(u << 1 | 1, x, c);
        tr[u].f = tr[u << 1].f * tr[u << 1 | 1].f;
    }
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
    }
    build(1, 1, n);
    LL ret = 0;
    while (m--) {
        int x, y;
        scanf("%d %d", &x, &y);
        modify(1, x, y);
        Matrix t = Matrix() * tr[1].f;
        ret += max(t[0][0], t[0][1]);
    }
    printf("%lld", ret);
    
    return 0;
}

 

参考资料

  题解 P3097 【[USACO13DEC]Optimal Milking G】:https://www.luogu.com.cn/article/0apre70k

  题解 P3097 【[USACO13DEC]Optimal Milking G】:https://www.luogu.com.cn/article/6hvc01gw

posted @ 2024-05-27 17:07  onlyblues  阅读(2)  评论(0编辑  收藏  举报
Web Analytics