P6772 [NOI2020] 美食家 题解

一道不错的题目。

一个朴素的 dp 就是 fi,x 表示时间 i 时从 1 走到 x 的最大美味值,就有转移 fi,vj=max{fiwj,uj+cvj+},结合性质 1 判下环有 50 分。

考虑 k=0 并且 wi=1 的情况,就是一个经典问题:nm 边,问恰好经过 k 条边情况下的最长路,有 fi,vj=max{fi1,uj+cvj}

考虑点权转边权,每一条边边权是指向点的点权,用邻接矩阵存一下就有 fi,vj=max{fi1,uj+duj,vj}

这实际上是一种矩阵形式,新定义矩阵乘法 C=A×BCi,j=max{Ai,k+Bk,j},然后可证这个运算满足结合律,因此可以考虑将 fi 看成一个行向量(一行的矩阵),就有 ft=f0×dt,其中 f0,1=c1 其余为 -INF,邻接矩阵 d 中没有边的都是 -INF,最后答案 ft,x0,x=1,如果是负的就说明无解。

然后考虑 wi1 的情况,考虑一条边 (u,v,w),拆成若干个点 ue1e2...ewv,这样就消除了这个情况,但是按边来拆点点数是 n+4mmn 大得多,不大行,因此考虑按点来拆,将一个点拆成 5 个点,u1u2u3u4u5,边权全部都是 0,一条边 (u,v,w) 就连边 uwv1,意义在于前 w1 条边边权都是 0 没有贡献,只有最后一条边有贡献,这样可以过 k=0 的点。

考虑 k0 的情况,由于 k200 因此考虑按照 k 个美食节(按时间升序排序)划分,对于每两个美食节之间我们使用朴素的 dp 转移,然后每转移到一个美食节 (t,x,y) 那么 ft,x0ft,x0+y,这样可以做到复杂度 O(k×(5n)3logt),能获得 85 分,logt 是每次朴素 dp 转移时指数复杂度。

注意到我们可以预处理出 d1,d2,d4,d8,...,d229,就是二进制处理,预处理的复杂度是 O((5n)3logt),这样每次朴素 dp 时我们可以直接二进制拆出来而不需要每次重复计算 d2p,又因为 ft 实际上是一个行向量(只有一行),所以 ft 乘上这些矩阵的单次实际复杂度是 O((5n)2logt),成功优化了矩阵快速幂的 (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;
}
posted @   Plozia  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示