[NOI2020] 美食家 题解

属于是将矩阵快速幂的绝大部分技巧用到了极致的一道题。

暴力部分

首先我们先考虑一个普通 DP。

定义 dpt,i 表示在时间为 t 时到达点 i 可以得到的愉悦值之和的最大值。

显然有 (i,j)Edpt+w,j=max(dpt,i+cj)。特判一下当前节点有美食节的情况即可,复杂度 O(T×(n+m))

矩阵快速幂&拆点优化

我们先暂时不考虑美食节的情况,这个后面再处理。

观察到这个 T 非常的大,但是 dptdpt+w 这个东西不太好,我们当然希望是按照普通的矩阵快速幂一样,是用 dptdpt+1 的转移矩阵进行矩阵快速幂优化。

我们考虑矩阵快速幂中一个拆点/拆边的技巧。(为啥我不知道捏)

首先一个直观的,对于边 (i,j,w),你可以把他拆成 (i,e1,0),(e1,e2,0),....(ew1,j,cj)。(拆掉之后这个三元组的含义也变了,最后那个是边权)

这样你的转移式就变成了 dpt,i+wdpt+1,j。这样就非常容易使用矩阵快速幂优化了。

当然这里有个小细节,由于 DP 是求的 max 这里是 max 加矩阵乘法,不是普通的矩阵乘法,这里说明一下。

但是这样的话,复杂度仍然达到了 O((n+4×m)3×logT)

我们发现这个 n,m 根本就不同阶,考虑把拆边变为拆点。

具体来说,我们将点 i 拆成 i1i2i3i4i5 边权均为 0,对于原图上的边 (i,j,w) 对应在拆了点的图上就是 (iw,j1,cj),可以发现这样的策略与刚刚拆边的效果是等价的。

此时时间复杂度来到 O((5×n)3×logT)

特殊点处理

在进行了进一步的优化之后,此时我们就需要考虑一下美食节的问题了。

由于美食节会将 ti 时,cxi+yi,改变我们的转移矩阵。故我们考虑将 ti 看作特殊点,先求出 ti1 时的 dpti1,x,然后对于 dpti,x 进行暴力的 O(n+m) 转移,然后再求出 ti+11dpti+11,x 的值..... 依此类推。

时间复杂度 O(k×(n+m)+k×(5×n)3×logT)

向量乘矩阵优化

我们发现我们为了求出 ti1 时的 dp 值,每次都需要进行 O(5×n)3×logT) 的矩阵快速幂。

有一个很逆天的东西是,转移矩阵自乘的时间复杂度是 O((5×n)3),但是初始矩阵乘转移矩阵复杂度是 O((5×n)2),而在我们的特殊点处理中我们进行了多次快速幂,大大增加了转移矩阵的自乘。

假设转移矩阵是 G,我们考虑预处理出 G20,G21,...G2logT 的所有矩阵,这样我们的每次快速幂就是用初始矩阵乘以转移矩阵,快速幂的总进行复杂度就减少为了 O((5×n)2×logT×k),总复杂度加上这个预处理就是 O(k×(n+m)+k×(5×n)2×logT+(5×n)3×logT),发现此时可以通过。

#include <bits/stdc++.h>
using namespace std;
#define maxn 505
#define ll long long
struct matrix
{
	vector<vector<ll> > a;
	matrix operator *(const matrix &c)const
	{
		matrix now;
		int n = a.size(), m = c.a.size(), l = c.a[0].size();
		now.a.resize(n);
		for (int i = 0; i < n; ++i) now.a[i].resize(l); 
		for (int i = 0; i < n; ++i)
		{
			for (int j = 0; j < l; ++j)
			{
				now.a[i][j] = INT_MIN;
				for (int k = 0; k < m; ++k)
				if(a[i][k] != INT_MIN && c.a[k][j] != INT_MIN)
				now.a[i][j] = max(now.a[i][j], a[i][k] + c.a[k][j]);
			}
		}
		return now;
	}
	matrix(){}
	matrix(int len1, int len2, int op = 1)
	{
		a.resize(len1);
		for (int i = 0; i < len1; ++i) a[i].resize(len2);
		if(op) for (int i = 0; i < len1; ++i) a[i][i] = 1;
	}
};
int n, m, T, k;
int c[maxn], num[maxn][6];
matrix trans[31], Now;
int t[maxn], x[maxn], y[maxn], id[maxn];
bool cmp(int x, int y)
{
	return t[x] < t[y];
}
int main()
{
	cin >> n >> m >> T >> k;
	for (int i = 1; i <= n; ++i) cin >> c[i];
	for (int i = 1; i <= n; ++i) for (int j = 1; j <= 5; ++j) num[i][j] = (i - 1) * 5 + j;
	matrix now(5 * n, 5 * n), ans(1, 5 * n);
	for (int i = 0; i < 5 * n; ++i) ans.a[0][i] = INT_MIN;
	ans.a[0][0] = c[1];
	for (int i = 0; i < 5 * n; ++i) for (int j = 0; j < 5 * n; ++j) now.a[i][j] = INT_MIN;
	for (int i = 1; i <= m; ++i)
	{
		int x, y, z;
		scanf("%d %d %d", &x, &y, &z);
		int zd = y;
		x = num[x][z], y = num[y][1];
		now.a[x - 1][y - 1] = c[zd];
	}
	for (int i = 1; i <= n; ++i) for (int j = 2; j <= 5; ++j) now.a[num[i][j - 1] - 1][num[i][j] - 1] = 0;
	int last = 0;
	trans[1] = now;
	for (int i = 2; i <= 30; ++i) trans[i] = trans[i - 1] * trans[i - 1];
	for (int i = 1; i <= k; ++i) scanf("%d %d %d", &t[i], &x[i], &y[i]), id[i] = i;
	stable_sort(id + 1, id + k + 1, cmp);
	for (int j = 1; j <= k; ++j)
	{
		int i = id[j];
		Now = now;
		for (int j = 0; j < 5 * n; ++j) if(Now.a[j][num[x[i]][1] - 1] != INT_MIN) Now.a[j][num[x[i]][1] - 1] += y[i];
		int need = t[i] - 1 - last;
		while(need)
		{
			int x = need & -need;
			ans = ans * trans[__lg(x) + 1];
			need -= x;
		}
		ans = ans * Now;
		last = t[i];
	}
	if(last < T)
	{
		int need = T - last;
		while(need)
		{
			int x = need & -need;
			ans = ans * trans[__lg(x) + 1];
			need -= x;
		}
	}
	if(ans.a[0][0] == INT_MIN) ans.a[0][0] = -1;
	cout << ans.a[0][0] << endl;
}
posted @   Saltyfish6  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
Document
点击右上角即可分享
微信分享提示