【NOI2020】美食家(矩阵)
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\) 时,可以得到的最大价值和。显然有:
\]
其中 \(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】美食家(矩阵)的更多相关文章
- 洛谷 P6772 - [NOI2020]美食家(广义矩阵快速幂)
题面传送门 题意: 有一张 \(n\) 个点 \(m\) 条边的有向图,第 \(0\) 天的时候你在 \(1\) 号城市,第 \(T\) 天的时候你要回到 \(1\) 号城市. 每条边上的边权表示从城 ...
- [NOI2020]美食家 题解
题意分析 给出一个带权有向图,要求从节点 $1$ 出发,经过恰好 $T$ 的边权和,回到节点 $1$ ,求可经过的最大点权和.特别地,经过的边权和达到部分特殊数时,会有某个点的点权发生改变. 思路分析 ...
- P6772 [NOI2020]美食家
题目大意 给你一个 \(n\) 个点,\(m\) 条边的有向图,每条边有一个权值 \(w_i\) ,每个节点有一个权值 \(a_i\) . 你从节点 \(1\) 出发,每经过一个节点就可以获得该点的权 ...
- [XIN算法应用]NOI2020美食家
XIN(\(updated 2021.6.4\)) 对于很多很多的题目,发现自己并不会之后,往往会直接冲上一个XIN队算法,然而,这样 \(\huge{\text{鲁莽}}\) 的行为只能获得 TLE ...
- [NOI2020] 美食家
很好,自己会做NOI签到题了,去年只要会这题,再多打点暴力,\(Ag\)到手,希望今年\(NOI\)同步赛过\(Ag\)线吧,得有点拿得出手的成绩证明啊. 考虑\(T\)非常大,\(n\)又很小. 想 ...
- XIN队算法
XIN队算法 注:名称由莫队算法改编而来 从luogu搬过来了... \(newly\;upd:2021.7.8\) \(newly\;upd:2021.6.6\) OI至高算法,只要XIN队算法打满 ...
- 矩阵乘法优化DP复习
前言 最近做毒瘤做多了--联赛难度的东西也该复习复习了. Warning:本文较长,难度分界线在"中场休息"部分,如果只想看普及难度的可以从第五部分直接到注意事项qwq 文中用(比 ...
- NOI2020网上同步赛 游记
Day1 预计得分:\(32pts\)(我裂开了--) T1 美食家 表示考试的时候想到了关于矩阵快速幂的想法,甚至连分段后怎么处理都想好了,但是没有想到拆点,还有不知道怎么处理重边(这个考虑是多余的 ...
- NOI2020 同步赛划水记
因为太菜了没去现场参加 NOI 就算去了估计也只能混个Fe(雾) "两天都会各有一道签到题,争取拿到70分.剩下的题每道题打30分暴力.每天130分,就能稳拿Ag了."--ls D ...
随机推荐
- Netlink 内核实现分析 3
Netlink IPC 数据结构 #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unuse ...
- python3:文件读写+with open as语句(转)
读写文件是最常见的IO操作.Python内置了读写文件的函数,用法和C是兼容的. 读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘, ...
- Linux操作系统选择
主流的操作系统 ubuntu centos debian oracle linux 主要使用的操作系统就是上面几个,主要是ubuntu和centos,debian是基于ubuntu改的,oracle ...
- Python_opencv库
1.车牌检测 ''' 项目名称:opencv/cv2 车牌检测 简介: 1.训练级联表 ***.xml [跳过...] 2.用如下代码加载级联表和目标图片识别车牌 注:推荐用anconda安装open ...
- Django 配置 Mysql
先安装mysqlclient pip install mysqlclient sttings中的 DATABASES = { 'default': { 'ENGINE': 'django.db.bac ...
- 利用CSS3实现div页面淡入动画特效
利用CSS3实现页面淡入动画特效 摘要 利用CSS3动画属性"@keyframes "可实现一些动态特效,具体语法和参数可以网上自行学习.这篇文章主要是实践应用一下这个动画属性 ...
- CVE-2017-11882利用
CVE-2017-11882是微软公布的远程执行漏洞,通杀所有office版本及Windows操作系统 工具使用 本文使用的EXP来源于unamer/CVE-2017-11882,然后结合MSF进行渗 ...
- C#实现SM2国密加密
本文主要讲解"国密加密算法"SM系列的C#实现方法,不涉及具体的算法剖析,在网络上找到的java实现方法比较少,切在跨语言加密解密上会存在一些问题,所以整理此文志之.JAVA实现参 ...
- 图片恢复有新招,EasyRecovery预览模式助你快速恢复
EasyRecovery作为一款数据恢复软件,因其便捷的操作.低廉的价格深受大家的喜爱.EasyRecovery具有"傻瓜式"操作,就算你是第一次接触这款软件,通过主页提示也能很快 ...
- leetcode133. 克隆图
给定无向连通图中一个节点的引用,返回该图的深拷贝(克隆).图中的每个节点都包含它的值 val(Int) 和其邻居的列表(list[Node]).示例: 输入:{"$id":&quo ...