1. 程式人生 > 實用技巧 >NOI_2020_D1_T1 美食家

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=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;
}