NOI_2020_D1_T1 美食家
在不考虑美食节的情况下,题目就是:从某个点出发,经过 \(T\) 时间后回到原点所能的取到的最大价值。
注意到边权 \(w_i\le 5\),那么这道题就跟 P4159 是一样的,对于每个城市拆分成五个以内的点,使得边的长度都变成 \(1\),这样就可以用矩阵进行优化,每次矩阵乘类似 Floyd 跑一个最长路。
那如果考虑美食节呢?我们又发现这个美食节个数 \(k\le 200\),也就是说可以分段跑,然后对于每一段结束之后加上这个美食节的贡献。这个时候你就可以拿到 \(55\) 分,后面 \(\operatorname{TLE}\)。
分析复杂度:
\(n\le 50,T\le 10^9\)
分段运算就几乎是乘上了个 \(200\) 的大常数,而矩阵乘法跑最长路每次运算又是 \(\operatorname O((5n)^3)\),最大可以达到 \(\operatorname O(250^3)\)。每段也不止跑一次,常数可以简单算成乘一个 \(\log T\),大概是 \(\operatorname O(10^{11})\),直接爆炸。除非把时限开到100s
考虑优化:
\(\operatorname A:\) 转移矩阵实际上是可以按二进制预处理好的,这样就省去了分段造成的重复运算。(我甚至先算出最高会有几位然后再做,因为当时同学在 luogu 上被卡到十几秒,交了好几次才过,luogu 评测机浮动, 我以为这是个需要疯狂卡常的题)
\(\operatorname B:\) 最重要的一个优化,能直接把 \(n\) 的幂次减 \(1\)。
我们现在有两种矩阵,一种是存贮跑了当前时间以后的答案,即从某个点出发跑了当前时间后到达一个点能拿到的最大愉悦值(不可能则为 \(-1\)),记这个矩阵为 \(ans\);一个是我们需要用到的转移矩阵的集合,记为 \(sum[]\)。
是可以将 \(ans\) 这个矩阵压为一维的,只存从点 \(1\) 出发跑了若干时间到达点 \(i\) 的最大愉悦值。为什么可以这样呢?首先我们最后需要的答案是 \(ans_{1,1}\)(表示从 \(1\) 出发花了 \(T\) 时间回到 \(1\) 能拿到的最大愉悦值),跟 \(1\) 无关的处理出来也用不上。
然后考虑维护过程中这些东西是否是无用的。
如果我要维护 \(ans_{1,1}\) ,它可以由 \(ans_{1,j}+sum[]_{j,1}\) 得来
\(\operatorname {Question}:\) 那我维护这个 \(ans_{1,j}\) 不是有可能要用到 \(ans_{i,j},i!=1\) 的吗?
\(\operatorname {Answer}:\) 对于运算
你需要用到的 \(ans_{i,j},i!=1\) 是往前两步的 \(ans\) 的,是可以由状态转移矩阵来更新的,并不会缺失状态。
这么说感觉有点抽象,用式子表达就是:
对于 \(ans_{1,i}\) 也是一样的
结论:对于任意的 \(ans_{i,j},i!=1\) 都是无用的状态
我是把 \(ans\) 矩阵只考虑第一行的状态,这样前面预处理的乘法和跑答案的乘法是不一样的,你反正 \(ans\) 都压成一维了干脆另外写个结构体,然后重载两次乘法
那么到这里就已经能过了。
然后还有个没啥屁用的优化。
\(\operatorname C:\) 拆点的时候需要一个开一个,不要直接开 \(n\times 5\) 个(只要数据好,完全可以变成无用的优化,但反正这个本身也不加复杂度,我平时也都是这么写的)
当时交上去发现跑得很快,挤到了第一页,然后在其他地方优化了下常数,加了个火车头,就成功跑到了最优解。
接下来附上巨丑代码:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MAN=250;
const int MAM=502;
struct milk
{
LL a[MAN+2][MAN+2];
inline void build(){memset(a,-0x3f,sizeof(a));}
};
struct stan
{
LL a[MAN+2];
};
struct cow
{
int t,x,y;
}b[MAM];
int n,m,t,k;
int nam;
int c[53];
int to[MAN];
milk sum[40];
milk st;
inline bool myru(cow x,cow y){return x.t<y.t;}//美 食 节 不 一 定 按 时 间 顺 序 给 ! ! !
inline stan operator *(stan x,milk y)//ans × sum[]
{
stan z;
memset(z.a,-0x3f,sizeof(z.a));
for(int i=1;i<=nam;i++)
for(int j=1;j<=nam;j++)
if(x.a[i]>=0&&y.a[i][j]>=0)
z.a[j]=max(z.a[j],x.a[i]+y.a[i][j]);
return z;
}
inline milk cheng(milk s)//因为只会用到自己乘自己,干脆写函数不重载了
{
milk z;
z.build();
for(int k=1;k<=nam;k++)
for(int x=1;x<=nam;x++)
for(int y=1;y<=nam;y++)
if(s.a[x][k]>=0&&s.a[k][y]>=0)z.a[x][y]=max(z.a[x][y],s.a[x][k]+s.a[k][y]);
return z;
}
LL rin()
{
LL s=0;
char c=getchar();
bool bj=0;
for(;(c>'9'||c<'0')&&c!='-';c=getchar());
if(c=='-')c=getchar(),bj=true;
for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
if(bj)return -s;
return s;
}
int main()
{
// freopen("delicacy.in","r",stdin);
// freopen("delicacy.out","w",stdout);
n=rin();m=rin();t=rin();k=rin();
st.build();
nam=n;
for(int i=1;i<=n;i++)c[i]=rin();
for(int i=1;i<=m;i++)
{
int x,y,z;
x=rin();y=rin();z=rin();
for(;z>1;z--,x=to[x])if(to[x]==0)to[x]=++nam,st.a[x][nam]=0;
st.a[x][y]=c[y];
}
for(int i=1;i<=k;i++)b[i].t=rin(),b[i].x=rin(),b[i].y=rin();
sort(b+1,b+k+1,myru);
int max_t=t-b[n].t;
for(int i=1;i<=k;i++)max_t=max(max_t,b[i].t-b[i-1].t);
int max_s=0;
for(;max_t>0;max_t>>=1)max_s++;
milk s=st;
sum[1]=st;
for(int i=2;i<=max_s;i++)s=sum[i]=cheng(s);
stan ans;
for(int i=2;i<=nam;i++)ans.a[i]=-0x3f3f3f3f3f3f3f3f;
ans.a[1]=c[1];//一开始出发有一次城市1的愉悦值
for(int i=1;i<=k;i++)//跑到每个美食节的举办时间
{
int t_s=b[i].t-b[i-1].t;
for(int i=1;t_s>0;i++,t_s>>=1)if(t_s&1)ans=ans*sum[i];
if(ans.a[b[i].x]>=0)ans.a[b[i].x]+=b[i].y;
}
int t_s=t-b[k].t;//所有美食节都结束了,跑到给定的结束时间T
for(int i=1;t_s>0;i++,t_s>>=1)if(t_s&1)ans=ans*sum[i];
LL ans_s=-1;
ans_s=max(ans_s,ans.a[1]);
printf("%lld",ans_s);
return 0;
}