[解题报告] [NOI2020]美食家

🚪传送门

题意

一张有向图, 点集为 \(V\), 边集为 \(E\).

一条边 \((u, v, w)\) 表示从 \(u\)\(v\) 需要 \(w\) 天的时间.

每个点有一个点权 \(c_i\), 每次经过点 \(i\), 可以获得 \(c_i\) 的收益.

\(K\) 个节日 \((t,x,y)\), 表示第 \(t\) 天在点 \(x\) 庆祝节日, 若玩家此时在点 \(x\), 则可获得 \(y\) 的收益.

求一条起点为 \(1\) 的回路, 使得花费时间为 \(T\) 天, 且总收益最大.

数据范围

\(|V| \le 50,\ |E| \le 250,\ T \le 10 ^9,\ K \le 200\)


思路

套路积累: 每次图上的点被它相邻的节点更新, 求更新 \(T\) 此后某个点的值 \(\rightarrow\) 考虑矩阵快速幂优化.


回到这一题, 先考虑暴力做法的转移式. 设 \(f_{i,j}\) 为第 \(j\) 天到达点 \(i\) 所得到收益的最大值, 转移为

\[f_{v,k} = \max\{f_{u,k - w} + c_v \mid (u, v, w) \in E\} \]

考虑 \(w = 1\) 的特殊情况, 则

\[f_{v,k} = \max\{f_{u,k - 1} + c_v \mid (u, v) \in E\} \]

若把 \(c_v\) 看作 \((u,v)\) 的边权, 则该转移式可以转化为矩阵乘法的形式, 即

\[A_k = A_{k - 1} * G \]

其中, \(G\) 为该图的邻接矩阵, 若存在 \((u,v) \in E\), 则 \(G_{u,v} = c_v\); 否则 \(G_{u,v} = -inf\). 矩阵运算 \(C = A * B\) 重定义为 \(C_{i,j} = \max \{A_{i,k} + B_{k,j}\}\).

可以证明这个运算有结合性, 所以可以使用矩阵快速幂优化.

而对于 \(K\) 个节日, 我们将它们以 \(t\) 为关键字从小到大排序, 每次用矩阵快速幂加速 从第 \(t_{i - 1}\) 天到第 \(t_i\) 天 的转移, 然后在矩阵 \(A\) 中加上对应的收益 (即 \(A_{1, x_i} += y_i\)).


现在考虑一般情况.

注意到 \(w <= 5\), 因此我们可以考虑拆点.

把点 \(u\) 拆成 \(u_1, u_2, u_3, u_4, u_5\). 需连接两种边

  1. 在新图上从 \(u_i\)\(u_{i+1}\) 连一条边权为 \(0\) 的边.
  2. 若原图存在一条边 \((u,v,w)\), 则在新图上从 \(u_{w}\)\(v_1\) 连一条权值为 \(c_v\) 的边.

这样, 就可以按照 \(w = 1\) 的情况时的方法做了.

大致计算一下复杂度: 一次矩阵快速幂的时间复杂度为 \(O((5n) ^ 3 \log T)\), 要进行 \(K\) 次, 那么总时间复杂度约为 \(O((5n) ^ 4 \log T)\), 数量级为 \(10 ^ {10}\), 无法通过.

我们可以应用矩阵快速幂的一个常用优化: 预处理出转移矩阵 \(G\)\(2^{1 \sim \log T}\) 次方, 即 \(G ^ 1, G^2 ,G ^ 4 \cdots\), 然后每次转移时只需要进行 \(\log T\) 次复杂度为 \(O(n^2)\) 的矩阵乘法 (因为矩阵 \(A\) 只有一行).

所以, 总复杂度为 \(O((5n) ^ 3 \log T + (5n)^2K \log T)\), 大约为 \(10^9\), 但实际上跑不满, 因为并不是每次转移都需要进行 \(\log T\) 次矩阵乘法, 再加上有 \(2s\) 的时限, 可以通过.

总结

这题的思路看了题解后其实觉得蛮套路的, 之前 NOI ONLINE 就有一道类似的题 (魔法师). 只要掌握了这个套路再加上拆点的小技巧就可以解决了.

如果有疑问或文章本身有错误, 欢迎留言.


代码

#include<bits/stdc++.h>

typedef long long ll;

using namespace std;

const int _ = 250 + 7;
const int L = 30;
const ll inf = 1e18;

int n, m, T, K, a[_];

struct MATRIX {
  int r, c;
  ll q[_][_];
  MATRIX operator * (const MATRIX &B) const {
    MATRIX C = {r, B.c};
    for (int i = 1; i <= r; ++i)
      for (int j = 1; j <= B.c; ++j) {
        C.q[i][j] = -inf;
        for (int k = 1; k <= c; ++k) 
          C.q[i][j] = max(C.q[i][j], q[i][k] + B.q[k][j]);
      }
    return C;
  }
}A, G[L + 7];

struct FESTIVAL {
  int t, x, y;
  bool operator < (const FESTIVAL &b) const { return t < b.t; }
}fes[_];

void Init() {
  cin >> n >> m >> T >> K;
  G[0] = {5 * n, 5 * n};
  for (int i = 1; i <= 5 * n; ++i)
    for (int j = 1; j <= 5 * n; ++j)
      G[0].q[i][j] = -inf;
  for (int i = 1; i <= n; ++i) {
    cin >> a[i];
    for (int j = 0; j < 4; ++j) G[0].q[j * n + i][(j + 1) * n + i] = 0;
  }
  for (int i = 1, u, v, w; i <= m; ++i) {
    cin >> u >> v >> w;
    G[0].q[(w - 1) * n + u][v] = a[v];
  }
  for (int i = 1; i <= L; ++i) G[i] = G[i - 1] * G[i - 1];
  for (int i = 1; i <= K; ++i) cin >> fes[i].t >> fes[i].x >> fes[i].y;
  sort(fes + 1, fes + 1 + K);
  A = {1, 5 * n}, A.q[1][1] = a[1];
  for (int i = 2; i <= 5 * n; ++i) A.q[1][i] = -inf;
}

void Run() {
  fes[0] = {0, 0, 0}, fes[K + 1] = {T, 0, 0};
  for (int i = 1, tmp; i <= K + 1; ++i) {
    tmp = fes[i].t - fes[i - 1].t;
    for (int j = 0; j <= L; ++j, tmp >>= 1)
      if (tmp & 1) A = A * G[j];
    A.q[1][fes[i].x] += fes[i].y;
  }
  if (A.q[1][1] < 0) puts("-1");
  else cout << A.q[1][1] << endl;
}

int main() {
  Init();
  Run();
  return 0;
}
posted @ 2020-09-05 11:33  BruceW  阅读(130)  评论(0编辑  收藏  举报