AtCoder Beginner Contest 164 比赛人数11302 比赛开始后15分钟看到所有题
AtCoder Beginner Contest 164 E Two Currencies Bellman-Ford优化思想+01背包+动归dp状态转移
总目录详见https://blog.csdn.net/mrcrack/article/details/104454762
在线测评地址https://atcoder.jp/contests/abc164/tasks/abc164_e
考察最短路,但换钱是个难题,会改变最短路径,在何处换钱,请注意,换钱很可能会改变路径走向。
dp[i][j]=k表示从节点1出发到达节点i,此时手头有j块银币,对应的最小消耗时间k,
三种状态(节点,银币数量,消耗时间),就用一个二维数组dp[][]给表示了.
Bellman-Ford算法分三种:Bellman-Ford算法,Bellman-Ford优化,Bellman-Ford队列优化(即SPFA算法)
因该题状态较多,若采用SPFA算法,可能会造成预先开的队列数组空间过大,超出内存限制,
故退一步,采用了Bellman-Ford优化。
看懂下面代码,读者需具备最短路算法,01背包算法。
基本思想:先有钱,再旅行,之后计算一无所有(手头0块银币)的最小消耗时间.
#include <cstdio>
#include <algorithm>
#define LL long long
#define M 3000
#define INF 1e15
//1e15由来:换钱耗时+旅行耗时=(D*A*N)+(B*N)50*=10^9*50*50+10^9*50
using namespace std;
//3010由来,旅行最多经过N个点(N-1条边),每条边最多花费50,故总花费50*50=2500,再大些,取3010
LL dp[55][3010];//换钱耗时+旅行耗时=(D*A*N)+(B*N)50*=10^9*50*50+10^9*50很明显int不够用
int c[55],d[55],head[55],tot;
struct node{
int to,next,a,b;
}e[105*2];//无向图
void add_edge(int u,int v,int a,int b){//邻接表
tot++,e[tot].to=v,e[tot].a=a,e[tot].b=b,e[tot].next=head[u],head[u]=tot;
}
int main(){
int n,m,s,u,v,a,b,i,j,flag;
scanf("%d%d%d",&n,&m,&s);
for(i=1;i<=m;i++){
scanf("%d%d%d%d",&u,&v,&a,&b);
add_edge(u,v,a,b),add_edge(v,u,a,b);//无向图
}
for(i=1;i<=n;i++)scanf("%d%d",&c[i],&d[i]);
for(i=1;i<=n;i++)
for(j=0;j<=M;j++)//注意j从0开始,因最终目标是求dp[i][0],故j从0开始
dp[i][j]=INF;
s=min(s,M);//银币太多,无用
dp[1][s]=0;//初始位置情况
while(1){
flag=0;
for(u=1;u<=n;u++){//u所处节点
for(j=0;j<=M;j++)//j手上银币数量
if(j>=c[u]&&dp[u][j]>dp[u][j-c[u]]+d[u]) dp[u][j]=dp[u][j-c[u]]+d[u];//先换钱,01背包,j-c[u]能装进c[u]变成j
for(j=0;j<=M;j++)
for(b=head[u];b;b=e[b].next){//再旅行
v=e[b].to;
if(j>=e[b].a&&dp[v][j-e[b].a]>dp[u][j]+e[b].b)//j消耗掉e[b].a,才能到j-e[b].a状态 v->u->1
flag=1,dp[v][j-e[b].a]=dp[u][j]+e[b].b;//动归dp,状态转移
}
for(j=M-1;j>=0;j--)dp[u][j]=min(dp[u][j],dp[u][j+1]);//清空手头的银币,但不消耗时间
}
if(!flag)break;//Bellman-Ford优化
}
for(i=2;i<=n;i++)printf("%lld\n",dp[i][0]);
return 0;
}