P2886 [USACO07NOV] Cow Relays G - 貪心
阿新 • • 發佈:2021-11-19
本文參考高逸涵的《部分貪心思想在資訊學競賽中的應用》。
一種 \(\mathcal{O}(T^2)\) 的做法
首先感性理解一下:當 \(N\) 很大的時候,最優策略一定是在某個邊權很小的邊上繞圈。
性質 1:只有可能在路徑上一條最短的邊上連續走多次。
證明:假如在其他邊上連續走了多次,那我們可以將路徑上來回走的一對邊 \((u\to v,v\to u)\) 刪去,換成在最短的邊上來回走,這樣一定不劣。
性質 2:只有路徑上的一條最短邊可能被在同一方向經過三次及以上,這裡“經過”不要求連續。
證明:假如另外一條邊被在同一方向經過三次及以上,那麼路徑上一定有包含這條邊的兩個環。若這兩個環中有至少一個長度為偶數,那麼可以刪去這個環並在最短邊上來回走;否則,兩個環的長度都是奇數,把兩個環都刪去,換成在最短邊上來回走即可。
結論:最優方案下,只會有至多一條邊被經過 \(>2\) 次。
設 \(f_{u,d}\) 表示從 \(S\) 到 \(u\),經過的邊數為 \(d\) 的最短路,顯然可以 \(\mathcal{O}(T^2)\) 預處理。同理,預處理 \(g_{u,d}\) 表示從 \(E\) 的。
列舉每條邊作為那條被多次經過的邊,並 \(\mathcal{O}(T)\) 算出最小值即可。具體計算方式可見程式碼中的 Calc
函式。
程式碼
Written by Alan_Zhao#include <iostream> #include <cstring> #include <algorithm> #include <vector> using namespace std; #define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti) #define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti) typedef long long ll; const int N=1005,M=105,Inf=0x3f3f3f3f; int n; int Get(int u){ static int num[N]{}; if(!num[u]) return num[u]=++n; return num[u]; } int t,m,S,T,f[M][M<<1],g[M][M<<1]; struct Edge{int u,v,w;}edge[M]; int Calc(int u,int len){ int res=Inf; For(c,0,1){ int mn=Inf,k=(t+c)%2-2; for(int j=m*2+c;j>=0;j-=2){ while(k<m*2&&t-j-k>1){ k+=2,mn+=2*len; mn=min(mn,g[u][k]); } if(mn>=Inf||f[u][j]>=Inf) continue; res=min(res,mn+f[u][j]+(t-j-k)*len); } } return res; } int main(){ cin>>t>>m>>S>>T; S=Get(S),T=Get(T); For(i,1,m){ cin>>edge[i].w>>edge[i].u>>edge[i].v; edge[i].u=Get(edge[i].u),edge[i].v=Get(edge[i].v); } memset(f,0x3f,sizeof f),memset(g,0x3f,sizeof g); f[S][0]=0,g[T][0]=0; For(i,1,m*2){ For(j,1,m){ f[edge[j].u][i]=min(f[edge[j].u][i],f[edge[j].v][i-1]+edge[j].w); f[edge[j].v][i]=min(f[edge[j].v][i],f[edge[j].u][i-1]+edge[j].w); g[edge[j].u][i]=min(g[edge[j].u][i],g[edge[j].v][i-1]+edge[j].w); g[edge[j].v][i]=min(g[edge[j].v][i],g[edge[j].u][i-1]+edge[j].w); } } int ans=Inf; For(i,1,m){ ans=min({ans,Calc(edge[i].u,edge[i].w),Calc(edge[i].v,edge[i].w)}); } cout<<ans; return 0; }