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

题目简介

\(n\) 个结点,\(m\) 条单向边。到达第 \(i\) 个结点即可获得权值 \(c_i\)。第 \(i\) 条单向边由 \(u_i\) 连向 \(v_i\) ,通过这条边需要 \(w_i\) 个时间。

又有 \(k\) 个事件,第 \(i\) 个事件规定在 \(t_i\) 时刻结点 \(x_i\) 的权值会变成 \(c_i+y_i\)

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

\(1\le n\le 50\)\(n\le m\le 501\)\(0\le k\le 200\)\(1\le t_i\le T\le 10^9\)
\(1\le w_i\le 5\)\(1\le c_i\le 52501\)\(1\le u_i,v_i,x_i\le n\)\(1\le y_i\le 10^9\)

分析

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

\[f_{i,x}=\max\{f_{i-w,y}\}+c_x+g_{i,x} \]

这里 \(f_{i,x}\) 表示到达花费 \(i\) 个时间到达结点 \(x\) 的最大权值。\(g_{i,x}\) 表示第 \(i\) 时刻 \(x\) 结点附加的权值。

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

\(f_i\) 视作一个矩阵,我们需要构造一个合适的矩阵 \(G\) ,使得

\[f_i\times G=f_{i+1} \]

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

\[C=A\times B,C[i][j]=\max{A[i][k]+B[k][j]} \]

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

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

我们在 \(k\) 个相邻的事件间,用 \(f_{t_i}=f_{t_{i-1}}\times G^{t_i-t_{i-1}+1}\),再令 \(f_{t_i,x_i}+=y_i\) 即可。

然后就 \(\mbox{TLE}\) 了……

会发现这个算法的时间复杂度是 \(\mbox{O}(k\ (5n)^3\ \log T)\)的。

可以进行二进制拆分,先需处理出 \(G,G^2,G^4,G^8,G^{16},\dots,G^{2^{29}},G^{2^{30}}\),将总时间复杂度优化为 \(\mbox{O}((5n)^3\log T+k\ (5n)^2\log T)\)

\(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 @ 2022-05-20 11:11  AlienCollapsar  阅读(129)  评论(0)    收藏  举报
// 生成目录索引列表 // ref: http://www.cnblogs.com/wangqiguo/p/4355032.html // modified by: zzq