「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}\) 表示到达花费 \(i\) 个时间到达结点 \(x\) 的最大权值。\(g_{i,x}\) 表示第 \(i\) 时刻 \(x\) 结点附加的权值。
接着你看,\(n,m\) 不大,这又是个图论题,那么多半都是矩阵优化了。
将 \(f_i\) 视作一个矩阵,我们需要构造一个合适的矩阵 \(G\) ,使得
根据本题目特性,我们将这里的矩阵乘法定义为:
但是问题是,平日里鄙人所见过的那些宛如沧海一粟的题目中,只需要填 \(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;
}

浙公网安备 33010602011771号