Luogu P6833 【[Cnoi2020]雷雨】
阿新 • • 發佈:2020-11-16
這道題賽時的時候想了一個奇怪的做法但是沒過,後來經過Stay_hungry的提示就碼了這道題。
雷電必定會在一點處分叉,分別電擊地上的兩個點,我們只需要列舉這個分叉點。那麼怎麼算出這個點和目標點的距離呢,很容易可以想到用最短路來求解。在仔細算一下複雜度\(O({V}log{E}+n^2)\) (\(V\)為點的個數 \(E\)為邊的個數)
這道題其實可以轉化為圖論題,就是從起點和兩個終點分別跑一遍最短路,然後列舉那個閃電⚡分開的點,在將三個\(dis_x\)(\(x\)為當前列舉的點的編號)加起來,取個\(Min\)就可以了。
細節處理都在程式碼中
//必須要開 long long 不然電阻R加起來會炸 //但是#define int long long 會MLE //所以就把幾個dis陣列開long long就好了 #include <bits/stdc++.h> #define Reg register using namespace std; const int N=1e3+10; inline long long Min(long long a,long long b) { return a<b?a:b; } inline int read() { int x=0,f=0; char c=getchar(); while(!isdigit(c)) f|=(c=='-'),c=getchar(); while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar(); return f?-x:x; } struct node { int u; long long d; inline bool operator <(const node &rhs) const { return d>rhs.d; } } ; struct Edge { int next,to,w; } edge[N*N*6]; int n=read(),m=read(),sx=read(),e1=read(),e2=read(),tot; long long ans=5e18; int head[N*N],a[N][N]; long long diss[N*N],dis1[N*N],dis2[N*N]; //建邊 inline void Addedge(int u,int v,int w) { edge[++tot]=(Edge){head[u],v,w}; head[u]=tot; } //Dijkstra inline void Dijkstra(int s,long long *dis) { for(Reg int i=1;i<=n*m;i++) dis[i]=5e18; //將起點的初值設定為這個位置的電阻值 if(s%m) dis[s]=a[s/m+1][s%m]; else dis[s]=a[s/m][m]; priority_queue<node>que; que.push((node){s,0}); while(!que.empty()) { node front=que.top(); que.pop(); int u=front.u; // if(d==dis[u]) for(Reg int i=head[u];i;i=edge[i].next) { int v=edge[i].to,w=edge[i].w; // cout<<u<<' '<<v<<' '<<w<<'\n'; if(dis[u]+w<dis[v]) { dis[v]=dis[u]+w; que.push((node){v,dis[v]}); } } } } signed main() { //將網格圖轉化為有向圖 for(Reg int i=1;i<=n;i++) for(Reg int j=1;j<=m;j++) { a[i][j]=read(); //將每個點的編號設為從左往右數,從上往下數第幾個 //連邊時連的是它四周的四個點連向它自己,所以邊權是為這個點的電阻值 if(j>1)//這是防止向上一個網格連的時候越界,下同 Addedge((i-1)*m+j-1,(i-1)*m+j,a[i][j]); if(j<m) Addedge((i-1)*m+j+1,(i-1)*m+j,a[i][j]); if(i>1) Addedge((i-2)*m+j,(i-1)*m+j,a[i][j]); if(i<n) Addedge(i*m+j,(i-1)*m+j,a[i][j]); } Dijkstra(sx,diss)/*,puts("---")*/,Dijkstra(m*(n-1)+e1,dis1)/*,puts("---")*/,Dijkstra(m*(n-1)+e2,dis2); //跑三遍Dijkstra //列舉中間點 for(Reg int i=1;i<=n;i++) for(Reg int j=1;j<=m;j++) //因為中間點會被計算三次,所以要減去兩次 ans=Min(ans,diss[(i-1)*m+j]+dis1[(i-1)*m+j]+dis2[(i-1)*m+j]-2*a[i][j])/*,printf("I=%d,J=%d,Dis=%d,%d,%d\n",i,j,diss[(i-1)*m+j],dis1[(i-1)*m+j],dis2[(i-1)*m+j])*/; cout<<ans<<'\n'; return 0; }