[NOI Online 2020 #1]魔法 題解
阿新 • • 發佈:2021-07-29
題意簡述
給定一個 \(n\) 個點 \(m\) 條邊的帶權有向圖,你可以進行至多 \(k\) 次操作,使得下一次通過路徑的權值變為其相反數,之後再變回來。
問從 \(1\) 號點到 \(n\) 號點的最短路。
\(n≤100,m≤2500,k≤10^6\)。
Solution
先考慮 \(70\) 分怎麼做:
\(k=0\) 時直接跑個 \(floyd\) 就好了。
\(k=1\) 時也很簡單,列舉對哪條邊使用魔法。
記 \(f(k,i,j)\) 為進行至多 \(k\) 次操作,從 \(i\) 到 \(j\) 的最短路,有方程 \(f(1,i,j)=min \left\{f(0,i,e_u)+f(0,e_v,j)-e_t\right\}\)
拓展到 \(k\) 較大時的情況,有 \(f(k,i,j)=min \left\{f(x,i,p)+f(k-x,p,j)\right\} \left(0<x<k\right)\)。
直接轉移是 \(O(n^3k)\) 的,顯然無法通過。
考慮優化,發現這個轉移方程的形式就非常得好,它是一個廣義矩陣乘法,滿足結合律,於是我們可以直接對其矩陣快速冪。
時間複雜度 \(O(n^3logk+ n^2m)\)
在 \(NOI2020\) 的 \(D1T1\) 也有用到這個東西,還是蠻重要的。
記得特判 \(k=0\)。
Code
#include<bits/stdc++.h> #define IL inline #define RE register #define ll long long #define N 110 #define M 2550 #define INF (1ll<<60) IL int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar(); return x*f; } int n,m,k; template<class T1,class T2>IL void cmin(T1 &x,T2 y){x=x<y?x:y;} struct Matrix { ll a[N][N]; Matrix(){memset(a,63,sizeof(a));} void copy(ll b[][N]){for(RE int i=1;i<=n;++i)for(RE int j=1;j<=n;++j)cmin(a[i][j],b[i][j]);} Matrix operator *(const Matrix &x)const { Matrix ret; for(RE int i=1;i<=n;++i) for(RE int j=1;j<=n;++j) for(RE int k=1;k<=n;++k) cmin(ret.a[i][j],a[i][k]+x.a[k][j]); return ret; } }base,ans; struct Edge{int u,v,t;}e[M]; ll dis[N][N]; IL void floyd() { for(RE int k=1;k<=n;++k) for(RE int i=1;i<=n;++i) for(RE int j=1;j<=n;++j) cmin(dis[i][j],dis[i][k]+dis[k][j]); } int main() { n=read(),m=read(),k=read(); memset(dis,63,sizeof(dis)); for(RE int i=1;i<=n;++i)dis[i][i]=0; for(RE int i=1;i<=m;++i) { e[i]=(Edge){read(),read(),read()}; dis[e[i].u][e[i].v]=e[i].t; } floyd(); if(!k)return printf("%lld",dis[1][n]),0; for(RE int i=1;i<=m;++i) for(RE int x=1;x<=n;++x) for(RE int y=1;y<=n;++y) cmin(base.a[x][y],dis[x][e[i].u]+dis[e[i].v][y]-e[i].t); base.copy(dis),ans.copy(dis); for(;k;k>>=1,base=base*base)if(k&1)ans=ans*base; printf("%lld",ans.a[1][n]); return 0; }