P6772 [NOI2020] 美食家 题解(矩阵加速图上dp常用思路)
P6772 [NOI2020] 美食家 题解(矩阵加速图上dp常用思路)
简要题面
给定一张 \(n\) 个点 \(m\) 条单向边的图,走这条边需要花费 \(w_i\) 的时间(以天为单位),现在有一个人从 \(1\) 号点出发,最后回到 \(1\) 号点,要求走了 恰好 为 \(T\) 天。
每经过一个点 \(i\),可以获得 \(C_i\) 的贡献,最后规定有 \(k\) 个特殊点,在走到那恰好为 \(T_i\) 天 时,且恰好在 \(X_i\) 时,可以获得 \(Y_i\) 的额外贡献
求最大贡献
\(1\le n\le 50 \le m \le501,0\le k\le200,1\le T_i,T\le10^9,1\le w\le 5\)
\(\mathcal{Solution.1} : 40pts\)
考虑朴素 \(dp\),我们发现,如果用天数作为主状态进行转移,这种定义是满足无后效性的,除此之外,我们定义另外一维表示现在是第几个点辅助我们转移。
则我们定义:\(dp_{i,j}\) 表示第 \(i\) 天走到 \(j\) 点时可以获得的最大贡献,我们写出状态转移方程:
然后特判 \(k\) 个特殊点的情况即可。
这种朴素 dp 没有任何问题,满足无后效性,时间复杂度为:\(\mathcal{O}(T(n+m))\),可以通过 40 分!
\(\mathcal{Solution.2}:65pts\)
看到 \(T\) 这么大,这个时候千万不要慌!思考一下什么东西可以干这么一个事情
矩阵快速幂,矩阵快速幂优化 dp !!
我们现在有了个这么思路,考虑怎么做,现在有两个问题:
- Q1:矩阵快速幂一般是一天天转移的,这个是几天几天转移的,不行!
- Q2:矩阵快速幂一般是边权,这个是点权,不行!
现在我们来思考怎么解决:
- 假 A1:
考虑将这个点到另外一个点的时间延长,变成一个一个,我们就会想到:将一个边拆成 \(w\) 条边,然后把这个 \(w\) 附到最后一条边上,表示这段路终于走完啦
也就是把:\((u,v,w)\rightarrow (u,v_1,0),(v_1,v_2,0)......(v_w-1,v,w)\),这样就好了
我们看下 \(w\) 的范围,诶 \(1\le w\le 5\),这绝对是正解!
然后你算一下点有多少个:\(n+4m\),emm,这 \(m\) 似乎有点大,额这(
- 真 A1:
拆边不行,这个点比边小多了,我们尝试拆点
如果刚才拆边可以理解为:放慢自己的移动速度,那我们拆点可以理解为:我们在这个点待 \(w-1\) 天,然后再瞬移过去!
怎么做到?我们考虑吧一个点拆成 \(5\) 个点,也就是 \((u,v,w)\rightarrow (u,u_1,0),(u_1,u_2,0).....(u_w,v,w)\)
这样就可以完美的解决了这个问题,一共有 \((5n)^3\) 个点
- A2:
你发现,你刚才已经做过了,也就是成功把点权变成了边权,这里就不说了
到这里我们再来考虑怎么矩阵优化:
我们再一次写出状态转移方程:
到了这里,就是一个裸的模版了,我们把 \(dp_i\) 看成一个矩阵,其实是个 \(1\times 5n\) 的长条,然后右边是领接矩阵
然后我们左边就是枚举每个 \(p\) 的过程,然后右边从上往下固定 \(j\) 去算 \(G_{p,j}\),即可,但是我们发现这里的矩阵乘法和我们的不一样
没关系,我们重新定义
定义广义矩阵乘法 \(C = A\oplus B\rightarrow C_{i,j} = \max_{k=1}^n A_{i,k}+B_{k,j}\),并且这个东西满足结合律,证明作者太菜了不会,谁会了踢我一下
时间复杂度为:\(\mathcal{O}((5n)^3 \log T)\),可以通过 \(k=0\) 的部分,结合之前的暴力分即可
\(\mathcal{Solution.3}:75pts\)
考虑原问题是一整段区间即:\([1,T]\),我们有 \(k\) 个点分割了他们,我们考虑一段一段进行计算,也就是先计算 \([1,X_1]\) 在计算 \([X_1+1,X_2]\),以此类推
我们考虑怎么加上额外的贡献
很简单,我们在他前一天停下来,加到矩阵里面,继续做就行
最后判一下有没有到终点就行了
这样做时间复杂度为:\(\mathcal{O}(k\log T (5n)^3)\),可以得到 75pts 的好成绩
\(\mathcal{Solution}.4:100pts\)
我们发现,只要在优化一个 \(n\) 差不多就行了,考虑矩阵快速幂的时间
我们发现,我们一直在乘 \(5n\times 5n\) 的矩阵,那个长条一直没管他,我们能不能乘多几次长条,不乘那个大矩阵呢?
其实是可以的,这里用到了一个常用思想:二进制分解
我们考虑现在是:\(A\oplus G^{p}\),然后我们把 \(p\) 做二进制分解假设成为 \(p_1……p_n\),然后答案就是:\(A\oplus G^{p1} \oplus G^{p2}……\)
然后这里就有个思路,我们预处理 \(G^{2^i}\),然后用上面的方法做就行了
预处理是 \(\mathcal{O}((5n)^3\log T)\),总时间复杂度为:\(\mathcal{O}((5n)^3\log T + k(5n)^2\log T)\),可以通过这题
Code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, m, T, k;
const int MAXN = 2.5e2 + 7, Inf = 1e18 + 7;
int C[MAXN], Id[MAXN][10], Id_cnt;
struct Matrix {
int a[MAXN][MAXN], n, m;
Matrix (int n = 0, int m = 0) : n(n), m(m) {
for (int i = 1; i <= MAXN - 7; i ++)
for (int j = 1; j <= MAXN - 7; j ++)
a[i][j] = -Inf;
}
Matrix operator * (const Matrix b) {
Matrix c = Matrix{n, b.m};
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= b.m; j ++)
for (int k = 1; k <= m; k++)
if (a[i][k] != -Inf && b.a[k][j] != -Inf)
c.a[i][j] = max(a[i][k] + b.a[k][j], c.a[i][j]);
return c;
}
void print() {
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
if (a[i][j] == -Inf) cout << "I" << (j == m ? '\n' : ' ');
else cout << a[i][j] << (j == m ? '\n' : ' ');
}
}E, G, A, K[50];
Matrix qpow(Matrix a, int b) {
Matrix ans = E;
while (b) {
if (b & 1) ans = ans * a;
a = a* a; b >>= 1;
}
return ans;
}
void init() {
E.n = E.m = 5 * n; for (int i = 1; i <= n; i ++) E.a[i][i] = 1;
A.n = 1, A.m = 5 * n; A.a[1][Id[1][1]] = C[1];
K[0] = G;
for (int i = 1; i <= 30; i ++)
K[i] = K[i - 1] * K[i - 1];
}
struct Node {
int t, x, y;
bool operator < (const Node other) const {
return t < other.t;
}
} Ev[MAXN];
void mul(Matrix &A, int b) {
for (int i = 0; i <= 30; i ++) {
if (b & (1 << i)) {
A = A * K[i];
}
}
}
signed main () {
cin >> n >> m >> T >> k;
for (int i = 1; i <= n; i ++) cin >> C[i];
G = Matrix{5 * n, 5 * n};
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= 5; j ++) {
Id[i][j] = ++ Id_cnt;
}
for (int j = 1; j < 5; j ++) {
G.a[Id[i][j]][Id[i][j + 1]] = 0;
}
}
for (int i = 1, u, v, w; i <= m; i ++) {
cin >> u >> v >> w;
G.a[Id[u][w]][Id[v][1]] = C[v];
}
for (int i = 1; i <= k; i ++)
cin >> Ev[i].t >> Ev[i].x >> Ev[i].y;
sort(Ev + 1, Ev + 1 + k);
init();
int last = 0;
for (int i = 1; i <= k; i ++) {
int pw = Ev[i].t - last;
mul(A, pw);
if (A.a[1][Id[Ev[i].x][1]] != -Inf) {
A.a[1][Id[Ev[i].x][1]] += Ev[i].y;
}
last = Ev[i].t;
}
if (last != T) mul(A, T - last);
if (A.a[1][Id[1][1]] == -Inf) cout << -1 << '\n';
else cout << A.a[1][Id[1][1]] << '\n';
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿