Description

给定一张有向图,\(n\) 个顶点,\(m\) 条边。第 \(i\) 条边从 \(u_i\) 到 \(v_i\),走完该边的用时为 \(w_i\)。每一个点有一个价值 \(c\),走到点 \(i\) 可以得到 \(c_i\) 的价值。

初始时间为 \(0\),你需要从起点 \(1\) 开始,走出一个回到 \(1\) 的有向环,耗时恰好为 \(T\)。最终得到的价值为所有经过的点的价值和。注意这里的环可以经过同个顶点多次,价值和也会被计算多次。

现在有 \(k\) 个附加元素,第 \(i\) 个附加元素有三个参数:\((t_i, x_i, y_i)\)。表示当恰好在 \(t_i\) 时间点到达顶点 \(x_i\) 时,可以得到 \(y_i\) 的额外的价值。

求最大的最终价值和。

Hint

  • \(1\le n\le 50\)
  • \(n\le m\le 501\)
  • \(0\le k\le 200\)
  • \(1\le t_i\le T\le 10^9\)
  • \(1\le w_i\le 5\)
  • \(1\le c_i\le 52501\)
  • \(1\le y_i\le 10^9\)

Solution

首先有一个简单朴素的动态规划:\(f(t, x)\) 表示在 \(t\) 时间点,走到顶点 \(x\) 时,可以得到的最大价值和。显然有:

\[f(t, x) = \max\limits_{(y, x, w) \in \text E} \{f(t-w, y)\} + c_x + g(t, x)
\]

其中 \(g(t, x)\) 表示 \(t\) 时间顶点 \(x\) 的附加值(美食节),如果没有就是 \(0\)。

这个做法是 \(O(m\times T)\) 的,期望 \(40\ \text{pts}\)。


考虑优化。为了方便,\(k\) 个额外的我们会在最后讨论,以下都假设 \(k=0\)。

我们发现,如果 \(w_i = 1\),那么所有边的转移都是“一步到位”的,不存在走了一天还在路上这种东西。那么这样显然可以 矩阵乘法 优化。

具体的,就是假设矩阵 \(B_t = \begin{bmatrix}f(t, 1)\\f(t, 2)\\ \vdots \\ f(t, n) \end{bmatrix}\),即时间点 \(t\) 下的所有状态。我们需要构造一个转移矩阵 \(A\),使得 \(B_{t+1} = A\circ B_t\)。其中 \(\circ\) 为自定义的一种“广义矩阵乘法”,计算公式即为上述状态转移方程:\(c_{i, j} = \max_{1\le k\le n} \{a_{i, k} + b_{k, j}\}\)。可以证明这样定义一定满足结合律。(\(A^x = A\circ A\circ \cdots \circ A\),\(x\) 个 \(A\))

不难发现转移的条件是边存在,于是 \(A\) 就是 反图 (带上价值)的邻接矩阵。为什么是反图?因为这里 \(A_{u, v}\) 的含义是 \(u\) 从 \(v\) 转移而来得到的价值 \(c_u\),并不是 \(u\to v\) 转移过去。

那么直接矩阵快速幂即可,\(B_t = A^t \circ B_0\),答案即为 \((B_t)_{1, 1}\) 的值,复杂度 \(O(n^3\log T)\)。


然而边长 \(w\) 并不是一,为了不让我们前面的努力白费, 我们尝试着将原图的边权“变成”\(1\)。本题中有一个突破口:\(w\le 5\),那么我们的机会来了。

拆边?我们发现点数的规模会达到 \(O(mw+n)\),不可承受。所以选择拆点。

我们把点 \(u\) 拆成五个:\(u_1, u_2, u_3, u_4, u_5\),然后连边 \(u_1\to u_2\to u_3\to u_4\to u_5\),边权都为 \(0\),边长为 \(1\),表示这些拆出来的边不会对答案有贡献。更新转移矩阵 \(A_{u_w, u_{w-1}} = 0\)。

如果原图中有一条边 \((u, v, w)\),那么我们实际的连边为 \(u_w \to v_1\)。不难发现,长度为 \(w\) 的边正好被分为了 \(w\) 段长度为 \(1\) 的边。然后更新转移矩阵 \(A_{v, u_{w}} = c_u\)。

最后得到了一个 \((nw)^2\) 大小的矩阵。

那么现在有了一个应付 \(k=0\) 的数据的做法。


考虑如何加入附加元素(美食节)的影响。发现整个时间段 \([0, T]\) 被时间点 \(t_1, t_2, \cdots , t_k\) 分为了一些段,那我们不如 一段段 地做。

具体的,当 \(t_{i-1}\) 刚做好时,我们直接跳到 \(t_i\),然后在点 \(x_i\) 的对应值加上 \(y_i\)。于是问题被完美地解决了……

显然还没有。这里有两个问题:

  • \(x_i\) 加到那个点上?
  • 复杂度好像有点炸……

下面给出解决方案:

  • 由于我们进行了拆点操作,点 \(x_i\) 应对于 \(5\) 个点中的哪一个呢?假设有一条 \(u_1 \to u_4 \to v_1\) 的路径,其对应的原图的边其实是 \((u, v, 4)\)。对于点 \(u_4\) 而言,它的实际含义其实是“在路上”。对于 \(u_2, u_3, u_5\) 同理。可见只有点 \(u_1\) 才是真正原图上的顶点。于是一个把 \(y_i\) 加在 \((x_i)_1\) 的位置上。
  • 算一下复杂度?我们最多有 \(k\) 次,每次 \(\log (t_i - t_{i-1})\) 次矩阵(方阵×方阵)乘法,总复杂度为 \(O((nw)^3\sum \log (t_i - t_{i-1}))\)。也就是说 我们可能会做 \(200\) 多次矩阵快速幂! 这里会用到一个神奇优化,详情见下:

我们预处理出 \(A\) 之后,再计算一个 \(P\)。其中 \(P_i = A^{2^i}\)。显然 \(P_i = P_{i-1}^2\),可以在 \(O((nw)^3\log T)\) 的时间内求出。

然后尝试着用 \(P\) 替代矩阵快速幂。我们考虑一下,我们需要求 \(B \circ A^x\) 的时候,快速幂是先计算 \(A^x\) 的,其中原理是 \(A^x = A^{e_1} \circ A^{e_2} \circ \cdots \circ A^{e_q}\),其中 \(e\) 是 \(x\) 二进制下的所有为 \(1\) 的位分别独立取出的值。而我们现在已经预处理好了这些 \(P\),如果我们直接用 \(B\) 每次对 \(P\) 做乘法,结果是一样的。

但这两者又有什么区别呢?当然有!我们发现 \(B\) 是一个 列向量,这个东西乘上 \(A\) 一次是 平方级别 的!

现在我们可以做到一次跳时间点 \(O((nw)^2\log(t_i - t_{i-1}))\),已经可以通过了。

总复杂度 \(O((nw)^3\log T + (nw)^2\sum\log(t_i - t_{i-1}))\)。

Code

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : NOI2020 美食家
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream> using namespace std;
typedef long long LL; const int N = 50 + 5;
const int W = 5;
const int logT = 31;
const int K = 200 + 5;
const LL inf = 5e16; int n, m, T, k;
int c[N];
struct activity {
int t, x, y;
} act[K << 1]; struct matrix {
LL e[N * W][N * W];
int R, C;
inline LL* operator [] (int p) { return e[p]; }
inline void set(LL v) {
for (int i = 1; i <= R; i++)
for (int j = 1; j <= C; j++)
e[i][j] = v;
}
} A, B, P[logT];
// P[k] : A^{2^k} inline matrix operator * (matrix a, matrix b) {
matrix c; c.R = a.R, c.C = b.C;
c.set(-inf);
for (int k = 1; k <= a.C; k++)
for (int i = 1; i <= a.R; i++)
for (int j = 1; j <= b.C; j++)
c[i][j] = max(c[i][j], a[i][k] + b[k][j]);
return c;
} void trans(matrix& B, int t) {
if (!t) return;
for (int i = 0; i < logT; i++)
if ((t >> i) & 1)
B = P[i] * B;
} signed main() {
freopen("delicacy3.in", "r", stdin);
// freopen("delicacy.out", "w", stdout); ios::sync_with_stdio(false);
cin >> n >> m >> T >> k; A.R = A.C = n * 5;
A.set(-inf); for (int i = 1; i <= n; i++)
for (int w = 1; w < W; w++)
A[i + n * w][i + n * (w - 1)] = 0;
for (int i = 1; i <= n; i++)
cin >> c[i];
for (int i = 1; i <= m; i++) {
int u, v, w; cin >> u >> v >> w;
A[v][u + n * (w - 1)] = c[u];
}
for (int i = 1; i <= k; i++)
cin >> act[i].t >> act[i].x >> act[i].y; P[0] = A;
for (int i = 1; i < logT; i++)
P[i] = P[i - 1] * P[i - 1];
B.R = n * 5, B.C = 1;
B.set(-inf);
B[1][1] = 0; sort(act + 1, act + 1 + k, [](activity& a, activity& b) {
return a.t < b.t;
});
int curT = 0;
for (int i = 1; i <= k; i++) {
int gap = act[i].t - curT;
trans(B, gap);
B[act[i].x][1] += act[i].y;
curT = act[i].t;
}
trans(B, T - curT); LL ans = B[1][1] + c[1];
cout << (ans < 0 ? -1 : ans) <<endl;
return 0;
}

【NOI2020】美食家(矩阵)的更多相关文章

  1. 洛谷 P6772 - [NOI2020]美食家(广义矩阵快速幂)

    题面传送门 题意: 有一张 \(n\) 个点 \(m\) 条边的有向图,第 \(0\) 天的时候你在 \(1\) 号城市,第 \(T\) 天的时候你要回到 \(1\) 号城市. 每条边上的边权表示从城 ...

  2. [NOI2020]美食家 题解

    题意分析 给出一个带权有向图,要求从节点 $1$ 出发,经过恰好 $T$ 的边权和,回到节点 $1$ ,求可经过的最大点权和.特别地,经过的边权和达到部分特殊数时,会有某个点的点权发生改变. 思路分析 ...

  3. P6772 [NOI2020]美食家

    题目大意 给你一个 \(n\) 个点,\(m\) 条边的有向图,每条边有一个权值 \(w_i\) ,每个节点有一个权值 \(a_i\) . 你从节点 \(1\) 出发,每经过一个节点就可以获得该点的权 ...

  4. [XIN算法应用]NOI2020美食家

    XIN(\(updated 2021.6.4\)) 对于很多很多的题目,发现自己并不会之后,往往会直接冲上一个XIN队算法,然而,这样 \(\huge{\text{鲁莽}}\) 的行为只能获得 TLE ...

  5. [NOI2020] 美食家

    很好,自己会做NOI签到题了,去年只要会这题,再多打点暴力,\(Ag\)到手,希望今年\(NOI\)同步赛过\(Ag\)线吧,得有点拿得出手的成绩证明啊. 考虑\(T\)非常大,\(n\)又很小. 想 ...

  6. XIN队算法

    XIN队算法 注:名称由莫队算法改编而来 从luogu搬过来了... \(newly\;upd:2021.7.8\) \(newly\;upd:2021.6.6\) OI至高算法,只要XIN队算法打满 ...

  7. 矩阵乘法优化DP复习

    前言 最近做毒瘤做多了--联赛难度的东西也该复习复习了. Warning:本文较长,难度分界线在"中场休息"部分,如果只想看普及难度的可以从第五部分直接到注意事项qwq 文中用(比 ...

  8. NOI2020网上同步赛 游记

    Day1 预计得分:\(32pts\)(我裂开了--) T1 美食家 表示考试的时候想到了关于矩阵快速幂的想法,甚至连分段后怎么处理都想好了,但是没有想到拆点,还有不知道怎么处理重边(这个考虑是多余的 ...

  9. NOI2020 同步赛划水记

    因为太菜了没去现场参加 NOI 就算去了估计也只能混个Fe(雾) "两天都会各有一道签到题,争取拿到70分.剩下的题每道题打30分暴力.每天130分,就能稳拿Ag了."--ls D ...

随机推荐

  1. one-wallhaven 一个壁纸程序

    one-wallhaven 一款基于 Electron 壁纸客户端 . gitee:https://gitee.com/ml13/wallhaven-electron github:https://g ...

  2. 1-06-2 Lambda表达式

    last modified:2020/10/31 1-06-3-Lambda表达式 6.3.1 为什么引入lambda表达式 lambda表达式是一个可传递的代码块,可以在以后执行一次或多次. 将一个 ...

  3. 如何在所有的mon的损坏情况下将数据恢复如初

    本篇主题 在mon无法启动,或者所有的mon的数据盘都损坏的情况下,如何把所有的数据恢复如初 写本章的缘由 在ceph中国的群里有看到一个技术人员有提到,在一次意外机房掉电后,三台mon的系统盘同时损 ...

  4. 面试常问的 25+ 个 Linux 命令

    作为一个Java开发人员,有些常用的Linux命令必须掌握.即时平时开发过程中不使用Linux(Unix)或者mac系统,也需要熟练掌握Linux命令.因为很多服务器上都是Linux系统.所以,要和服 ...

  5. C语言设计模式(自我揣摩)

    NBModule.h #ifndef _NBMODULEFRAME_H__ #define _NBMODULEFRAME_H__ #include "total.h" enum N ...

  6. LeetCode 中等题解(3)

    34 在排序数组中查找元素的第一个和最后一个位置 Question 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置. 你的算法时间复杂 ...

  7. vue中使用transition和animate.css动画效果

    一.单个动画中,使用div中引用animate动画 1.下载依赖 npm install animate.css –save 2.main.js中全局引用 import animate from 'a ...

  8. Java Web 会话技术总结

    会话技术 会话概念 一次会话中包含多次请求和响应. 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止,一次会话结束. 会话的功能 在一次会话的范围内的多次请求间,共享数据. 会 ...

  9. 灵彤彤女版PUA机构火了!“我花了8888报名学撩汉,却被导师骗去卖身。

    最近,几张女PUA机构的导师和课程海报在社交网络广泛刷屏. ​ 而社长觉得自己可以去潜心研究一下,为什么有女PUA机构的这种课程呢? 爱情的确是一门玄学. 精通此技能的女孩桃花不断,前任和现任无缝切换 ...

  10. Java基础教程——缓冲流

    缓冲流 "缓冲流"也叫"包装流",是对基本输入输出流的增强: 字节缓冲流: BufferedInputStream , BufferedOutputStream ...