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 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=ans \times sum[x_1] \times sum[x_2] \dots \times sum[x_k] \]
你需要用到的 \(ans_{i,j},i!=1\) 是往前兩步的 \(ans\) 的,是可以由狀態轉移矩陣來更新的,並不會缺失狀態。
這麼說感覺有點抽象,用式子表達就是:
\[ans_{1,1}=ans_{1,1}+sum[x_p]_{1,j}+sum[x_{p+1}]_{j,1} \]
對於 \(ans_{1,i}\) 也是一樣的
\[ans_{1,i}=ans_{1,i}+sum[x_p]_{i,j}+sum[x_{p+1}]_{j,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;
}