P6772 [NOI2020] 美食家 题解
一道不错的题目。
一个朴素的 dp 就是 \(f_{i,x}\) 表示时间 \(i\) 时从 1 走到 \(x\) 的最大美味值,就有转移 \(f_{i,v_j}=\max\{f_{i-w_j,u_j}+c_{v_j}+美食节贡献\}\),结合性质 1 判下环有 50 分。
考虑 \(k=0\) 并且 \(w_i=1\) 的情况,就是一个经典问题:\(n\) 点 \(m\) 边,问恰好经过 \(k\) 条边情况下的最长路,有 \(f_{i,v_j}=\max\{f_{i-1,u_j}+c_{v_j}\}\)。
考虑点权转边权,每一条边边权是指向点的点权,用邻接矩阵存一下就有 \(f_{i,v_j}=\max\{f_{i-1,u_j}+d_{u_j,v_j}\}\)。
这实际上是一种矩阵形式,新定义矩阵乘法 \(C=A\times B\) 有 \(C_{i,j}=\max\{A_{i,k}+B_{k,j}\}\),然后可证这个运算满足结合律,因此可以考虑将 \(f_i\) 看成一个行向量(一行的矩阵),就有 \(f_t=f_0\times d^t\),其中 \(f_{0,1}=c_1\) 其余为 -INF,邻接矩阵 \(d\) 中没有边的都是 -INF,最后答案 \(f_{t,x_0},x=1\),如果是负的就说明无解。
然后考虑 \(w_i\ne1\) 的情况,考虑一条边 \((u,v,w)\),拆成若干个点 \(u\to e_1\to e_2\to...\to e_w\to v\),这样就消除了这个情况,但是按边来拆点点数是 \(n+4m\),\(m\) 比 \(n\) 大得多,不大行,因此考虑按点来拆,将一个点拆成 5 个点,\(u_1\to u_2\to u_3\to u_4\to u_5\),边权全部都是 0,一条边 \((u,v,w)\) 就连边 \(u_w\to v_1\),意义在于前 \(w-1\) 条边边权都是 0 没有贡献,只有最后一条边有贡献,这样可以过 \(k=0\) 的点。
考虑 \(k\ne0\) 的情况,由于 \(k\le200\) 因此考虑按照 \(k\) 个美食节(按时间升序排序)划分,对于每两个美食节之间我们使用朴素的 dp 转移,然后每转移到一个美食节 \((t,x,y)\) 那么 \(f_{t,x_0}\leftarrow f_{t,x_0}+y\),这样可以做到复杂度 \(O(k\times(5n)^3\log t)\),能获得 85 分,\(\log t\) 是每次朴素 dp 转移时指数复杂度。
注意到我们可以预处理出 \(d^1,d^2,d^4,d^8,...,d^{2^{29}}\),就是二进制处理,预处理的复杂度是 \(O((5n)^3\log t)\),这样每次朴素 dp 时我们可以直接二进制拆出来而不需要每次重复计算 \(d^{2^p}\),又因为 \(f_t\) 实际上是一个行向量(只有一行),所以 \(f_t\) 乘上这些矩阵的单次实际复杂度是 \(O((5n)^2\log t)\),成功优化了矩阵快速幂的 \((5n)^3\) 复杂度,两个复杂度加起来就是总复杂度可以过。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P6772 [NOI2020] 美食家
Date:2022/10/9
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
LL Max(LL fir, LL sec) { return (fir > sec) ? fir : sec; }
const int MAXN = 50 + 5, MAXM = 501 + 5, MAXK = 200 + 5;
int n, m, t, k, ys[MAXN][7];
LL c[MAXN];
struct node
{
int t, x; LL y;
bool operator <(const node &fir)const { return t < fir.t; }
}a[MAXK];
struct Matrix
{
int n, m; LL a[MAXN * 5][MAXN * 5];
Matrix operator *(const Matrix &fir)
{
Matrix tmp; tmp.n = n, tmp.m = fir.m; memset(tmp.a, -0x3f, sizeof(tmp.a));
for (int i = 1; i <= n; ++i)
for (int k = 1; k <= m; ++k)
for (int j = 1; j <= fir.m; ++j)
if (a[i][k] >= 0 && fir.a[k][j] >= 0) tmp.a[i][j] = Max(tmp.a[i][j], a[i][k] + fir.a[k][j]);
return tmp;
}
}Base, dis, fact[30];
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
int main()
{
n = Read(), m = Read(), t = Read(), k = Read(); dis.n = 5 * n, dis.m = 5 * n; memset(dis.a, -0x3f, sizeof(dis.a));
for (int i = 1; i <= n; ++i) c[i] = Read();
for (int i = 1; i <= n; ++i) for (int j = 1; j <= 5; ++j) ys[i][j] = 5 * (i - 1) + j;
for (int i = 1; i <= n; ++i) for (int j = 1; j < 5; ++j) dis.a[ys[i][j]][ys[i][j + 1]] = 0;
for (int i = 1; i <= m; ++i)
{
int x = Read(), y = Read(), z = Read();
dis.a[ys[x][z]][ys[y][1]] = c[y];
}
fact[0] = dis; for (int i = 1; i <= 29; ++i) fact[i] = fact[i - 1] * fact[i - 1];
for (int i = 1; i <= k; ++i) a[i].t = Read(), a[i].x = Read(), a[i].y = Read();
std::sort(a + 1, a + k + 1); Base.n = 1, Base.m = 5 * n; memset(Base.a, -0x3f, sizeof(Base.a)); Base.a[1][1] = c[1];
int Last = 0; a[k + 1] = (node){t, 0, 0};
for (int i = 1; i <= k + 1; ++i)
{
int tmp = a[i].t - Last;
for (int j = 0; j <= 29; ++j) if ((tmp >> j) & 1) Base = Base * fact[j];
if (Base.a[1][ys[a[i].x][1]] >= 0) Base.a[1][ys[a[i].x][1]] += a[i].y; Last = a[i].t;
}
if (Base.a[1][1] < 0) puts("-1"); else printf("%lld\n", Base.a[1][1]); return 0;
}