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. 测试_QTP简介

    一:什么是QTP? QTP(QuickTest Professional)是一款自动化测试工具,自动化测试就是利用计算机模拟人进行测试,也就是开发一套代码测试另一套代码. QTP主要用它来执行重复的手 ...

  2. C#高级编程之泛型二(泛型类型约束、类型、反射与泛型)

    泛型类型约束 简言之:对泛型类型进行约束,细化,限定. MSDN的定义:泛型定义中的 where 子句指定对用作泛型类型.方法.委托或本地函数中类型参数的参数类型的约束,意思就是可以有泛型类.泛型方法 ...

  3. 凭借着这份面经,我拿下了字节,美团的offer!

    最近经常有粉丝私信问我问了一些诸如秋招该怎么复习的问题,我就想顺便把回答整理发一发.我也是把之前面试的一些经历经验和身边的人面试的经验总结了一下放在下面. 前期准备规划: 如果秋招的话一般过年回来就可 ...

  4. 面试官:小伙子,你给我说一下HashMap 为什么线程不安全?

    前言:我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但是其线程不安全主要体现在什么地方呢,本文将对该问题进行解密. 1.jdk1.7中的HashMap 在jdk1.8中对HashM ...

  5. PVE简单迁移虚拟机

    工作中有2台PVE节点,但是没有做集群,如果有集群可以很方便的进行迁移.本次迁移的目的是: 目前有一台PVE1节点装的虚机资源使用较多,想迁移某台虚机到另一台PVE2. 1 备份 备份在web页面操作 ...

  6. 遇到 ''isSort()''declared here, later in the translation unit

    在编写代码时,遇到 在原来的代码中出现这个问题 原来的代码: //3 计算排序时间 template<typename T> void testSort(string sortName, ...

  7. 巧妙运用Camtasia制作爱豆的动感影集

    对于追星族来说,收集爱豆的图片.视频是日常必做的事情,而对于进阶型的追星族来说,为爱豆自制各种精美的视频.影集等,会让自己追星之路显得更为充实. 我们可以借助Camtasia教程录制软件为爱豆制作各种 ...

  8. 思维导图软件iMindMap怎么用模板制作思维导图

    随着思维导图的不断发展,市场上相关的软件也越来越多.像XMind.MindManager等.每一款软件都有它独特的亮点.作为众多思维导图软件中的一款,iMindMap算是比较亮眼的了.现在很多人都在用 ...

  9. CSP2020 游记

    Day -28 后天就初赛了,考了一套模拟题,自闭,心态爆炸,感觉退役不远了 Day -26(初赛) 香农是谁??? 手写随机nth_element与O(n)的哈希表??? 阅读程序T2时间复杂度分析 ...

  10. Java蓝桥杯练习题——求小数n位后3个数

    求整数除法小数点后第n位开始的3位数 位数不足的补0,如0.125小数第3位后三位:0.12500→500 输入格式:a b n,空格分开,a是被除数,b是除数,n是小数后的位置 输出格式:3位数字, ...