【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 ...
随机推荐
- MySQL索引结构之B+树索引(面)
首先要明白索引(index)是在存储引擎(storage engine)层面实现的,而不是server层面.不是所有的存储引擎都支持所有的索引类型.即使多个存储引擎支持某一索引类型,它们的实现和行为也 ...
- android intent-filter 注册网页链接打开app
如下实现注册m.hao123.com的链接: <intent-filter><category android:name="android.intent.category. ...
- linux shell简单快捷方式与通配符(元字符)echo -e文本显示颜色
1.shell常用快捷方式 ^R 搜索历史命令^D 退出^A 光标移动到命令行最前^E 光标移动到命令行最后^L 清屏^U 光标之前删除^K 光标之后删除^Y 撤销^S 锁屏^Q 解锁 2.多条命令执 ...
- 企业级工作流解决方案(十二)--集成Abp和ng-alain--用户身份认证与权限验证
多租户 如果系统需要支持多租户,那么最好事先定义好多租户的存储部署方式,Abp提供了几种方式,根据需要选择,每一个用户身份认证与权限验证都需要完全的隔离 这里设计的权限数据全部存储在缓存中,每个租户单 ...
- PVE简单迁移虚拟机
工作中有2台PVE节点,但是没有做集群,如果有集群可以很方便的进行迁移.本次迁移的目的是: 目前有一台PVE1节点装的虚机资源使用较多,想迁移某台虚机到另一台PVE2. 1 备份 备份在web页面操作 ...
- 在线思维导图Ayoa可以用来梳理双十一优惠规则哦
一年一度的双十一又要来了,小伙伴们是否准备好开始买买买了呢?今年双十一,遇上英雄联盟S10总决赛,1/4决赛苏宁对上京东也让这个"电商大战"产生了很多有趣的梗.当然在玩梗的同时,广 ...
- 用CorelDRAW来制作产品结构图的方法
一.产品结构图的重要性 随着我国经济不断的高速发展,大家的生活水平不断提高,我们将会在生活生产中越来越多的,遇到许多各种各样的生产产品和生活消费品.科技的飞速进步,更是使这些产品.消费品包含了很强的科 ...
- FL Studio中如何进行工具栏编辑
菜单工具栏是我们使用FL Studio时经常需要使用的一个功能,那么,除了软件默认的菜单工具栏,我们应该如何编辑菜单工具栏呢? 图1:工具栏编辑 想要编辑更改默认菜单栏,我们只需要鼠标右键单击菜单工具 ...
- 仅需几步,EasyRecovery就能恢复RAID数据
RAID是什么?RAID是具有冗余能力的磁盘阵列,简单的来说就是把相同的数据存储在多个硬盘的不同的地方. 在了解了RAID之后就会知道,RAID中的数据也是有可能丢失的,虽然RAID数据丢失会引发一系 ...
- Elements-of-Python_02_DataType
(内容包括数据类型,运算符) 1.数据类型Data Types 1.1 简介Brief Introduction Python3中的数据类型,类型关键字,赋值,见下表: 1.1数字Number Pyt ...