「NOI2020」美食家 题解 (矩阵优化DP)

题目简介

n 个结点,m 条单向边。到达第 i 个结点即可获得权值 ci。第 i 条单向边由 ui 连向 vi ,通过这条边需要 wi 个时间。

又有 k 个事件,第 i 个事件规定在 ti 时刻结点 xi 的权值会变成 ci+yi

现在共 T 个时间,你需要在第 0 天从结点 1 出发,第 T 天回到结点 1,中途不做停留。问最终你能获得的最大权值为多少。

1n50nm5010k2001tiT109
1wi51ci525011ui,vi,xin1yi109

分析

最朴素,最容易想到(好像本蒟蒻并没有想到)DP 是:

fi,x=max{fiw,y}+cx+gi,x

这里 fi,x 表示到达花费 i 个时间到达结点 x 的最大权值。gi,x 表示第 i 时刻 x 结点附加的权值。

接着你看,n,m 不大,这又是个图论题,那么多半都是矩阵优化了。

fi 视作一个矩阵,我们需要构造一个合适的矩阵 G ,使得

fi×G=fi+1

根据本题目特性,我们将这里的矩阵乘法定义为:

C=A×BC[i][j]=maxA[i][k]+B[k][j]

但是问题是,平日里鄙人所见过的那些宛如沧海一粟的题目中,只需要填 G[ui][vi]=val 就行了,每乘一次就是转移一条边。但是如今如果贸然填 G[ui][vi]=cvi 的话,就直接忽略了题目中 wi 的限制 ,应当怎么转换呢?

实际上,在这 wi 天中途,我们可以看作是到达了一个没有权值的结点,所以我们只需要一条边拆成 wi 条边(这个理解方法有很多,也可以看作拆点)即可。将前 wi1 条边权值赋为 0 ,将第 wi 条赋值为 cvi。具体实现是多加一些点,可以看代码。

我们在 k 个相邻的事件间,用 fti=fti1×Gtiti1+1,再令 fti,xi+=yi 即可。

然后就 TLE 了……

会发现这个算法的时间复杂度是 O(k (5n)3 logT)的。

可以进行二进制拆分,先需处理出 G,G2,G4,G8,G16,,G229,G230,将总时间复杂度优化为 O((5n)3logT+k (5n)2logT)

AC Code

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
using ll=long long;
int c[55];
int id[515][8],tot;
struct matrix{
	ll mat[255][255];
	matrix(){memset(mat,0xcf,sizeof mat);}
	ll* operator[](int x){return mat[x];}
	matrix operator*(matrix& b){
		matrix c;
		for(int i=1;i<=tot;++i)
			for(int j=1;j<=tot;++j)
				for(int k=1;k<=tot;++k)
					c[i][j]=max(c[i][j],mat[i][k]+b[k][j]);
		return c;
	}
}g[32];
struct event{
	int t,x,y;
	bool operator<(const event& sec)const{return t<sec.t;}
}a[255];
ll ans[255];
ll tmp[255];
void mul(int p){
	for(int i=1;i<=tot;++i)tmp[i]=-1e18;
	for(int i=1;i<=tot;++i)
		for(int j=1;j<=tot;++j)
			tmp[j]=max(tmp[j],1ll*(ans[i]+g[p][i][j]));
	for(int i=1;i<=tot;++i)ans[i]=tmp[i];
}
int main(){
	int n,m,T,K;cin>>n>>m>>T>>K;
	for(int i=1;i<=n;++i)cin>>c[i];
	for(int i=1;i<=n;++i)id[i][0]=++tot;
	for(int i=1;i<=m;++i){
		int x,y,z;cin>>x>>y>>z;
		for(int j=1;j<z;++j)if(!id[x][j])id[x][j]=++tot;
		for(int j=1;j<z;++j)g[0][id[x][j-1]][id[x][j]]=0;
		g[0][id[x][z-1]][y]=c[y];
	}
	for(int i=1;i<=K;++i)cin>>a[i].t>>a[i].x>>a[i].y;
	sort(a+1,a+K+1);
	for(int i=1;i<=30;++i)g[i]=g[i-1]*g[i-1];
	ans[1]=c[1];
	for(int i=2;i<=tot;++i)ans[i]=-1e18;
	a[K+1].t=T;
	for(int i=1;i<=K+1;++i){
		int d=a[i].t-a[i-1].t;
		for(int j=30;~j;--j)if((d>>j)&1)mul(j);
		ans[a[i].x]+=a[i].y;
	}
	if(ans[1]>=0)cout<<ans[1]<<'\n';
	else cout<<-1<<'\n';
	return 0;
}

EOF

posted @   AlienCollapsar  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
// 生成目录索引列表 // ref: http://www.cnblogs.com/wangqiguo/p/4355032.html // modified by: zzq
点击右上角即可分享
微信分享提示