P6772 [NOI2020] 美食家 题解(矩阵加速图上dp常用思路)

发布时间 2023-08-19 00:10:11作者: Phrvth

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\) 点时可以获得的最大贡献,我们写出状态转移方程:

\[dp_{i,j}=\max_{p\rightarrow j \in G}dp_{i-W_p,p}+C_{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,j}=\max_{p\rightarrow j\in G} dp_{i-1,p}+G_{p,j} \]

到了这里,就是一个裸的模版了,我们把 \(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;
}

完结撒花✿✿ヽ(°▽°)ノ✿