P2886 [USACO07NOV] Cow Relays G - 贪心
本文参考高逸涵的《部分贪心思想在信息学竞赛中的应用》。
一种 \(\mathcal{O}(T^2)\) 的做法
首先感性理解一下:当 \(N\) 很大的时候,最优策略一定是在某个边权很小的边上绕圈。
性质 1:只有可能在路径上一条最短的边上连续走多次。
证明:假如在其他边上连续走了多次,那我们可以将路径上来回走的一对边 \((u\to v,v\to u)\) 删去,换成在最短的边上来回走,这样一定不劣。
性质 2:只有路径上的一条最短边可能被在同一方向经过三次及以上,这里“经过”不要求连续。
证明:假如另外一条边被在同一方向经过三次及以上,那么路径上一定有包含这条边的两个环。若这两个环中有至少一个长度为偶数,那么可以删去这个环并在最短边上来回走;否则,两个环的长度都是奇数,把两个环都删去,换成在最短边上来回走即可。
结论:最优方案下,只会有至多一条边被经过 \(>2\) 次。
设 \(f_{u,d}\) 表示从 \(S\) 到 \(u\),经过的边数为 \(d\) 的最短路。由于每条边都被经过 \(\le 2\) 次,只需算出 \(d\le 2T\) 的 \(f\) 即可。于是可以 \(\mathcal{O}(T^2)\) 预处理。同理,预处理 \(g_{u,d}\) 表示从 \(E\) 的。
枚举每条边作为那条被多次经过的边,并 \(\mathcal{O}(T)\) 算出最小值即可。具体计算方式可见代码中的 Calc
函数。
代码
#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;
}
Written by Alan_Zhao