[ACM]【Dijkstra/DP】Atcoder 164 Two Currencies

Two Currencies

传送门
题意:一个图,要求从起点到各个点的最短时间。其中,每条路要花费一定钱a,一定时间b。起初s元,有无限金币可换钱。每个点都可以换无限数目的钱,每次得到c元,花费d时间。
[ACM]【Dijkstra/DP】Atcoder 164 Two Currencies_第1张图片

思路:

会Dijkstra却不会带上dp的Dijkstra的我被自己菜哭,想了好久怎么处理每个点换多少钱,果然贪心不行dp来凑
其实思路不难,就是在图上进行dp

每个状态都做一次决策,要不要换钱。状态有两个,一个是dp的状态;一个是priority_queue优先队列中存的状态。状态 d p [ i ] [ j ] dp[i][j] dp[i][j]为到达点 i i i还剩余 j j j元的最短时间。队列中的状态为三元组 t u p l e ( t , u , w ) tuple(t,u,w) tuple(t,u,w)即此时时间过去了t秒,处于点u,还有w元。队列状态之间的转移,通过①连接两个点的边②换一次钱。按照Dijkstra的套路,从近到远(所需时间小到大)排序,抽取最近的点进行dp状态更新和队列状态转移。

与传统的Dijkstra不同的是,传统用vis数组记录有哪些点进来过,有哪些没有。这里通过是否 d p [ u ] [ w ] > t dp[u][w] > t dp[u][w]>t判重。(其实此处根本不需要判重,因为这里Dijkstra队列的形成必是时间递进的;而且有背包的上限,状态不会无限增加)

首先,我们得找到到达一个目的地可能需要的最多钱。来作为dp的背包上限。一共50个点,最远的地方是走了49条路,每条路最多花费50元,那么上限就是49×50=2450。即所有队列状态和dp状态的w小于等于2450。

接下来参考代码。

代码:

#include
using namespace std;
typedef long long ll;
const int maxn=55;
const int maxm=2466;
const ll inf=0x3f3f3f3f3f3f3f3f;//long long最大值,8个3f
#define tp tuple

vector<tp> G[maxn];//存每个点所能到达的点
ll c[maxn],d[maxn];//此点换钱的钱数和花费时间
ll dp[maxn][maxm];

int main(){
	int n,m,s;
	scanf("%d%d%d",&n,&m,&s);
	s=min(2450,s);//防止越界,取2450(最大容量)以下。
	for(int i=1;i<=m;i++){
		ll u,v,a,b;
		scanf("%lld%lld%lld%lld",&u,&v,&a,&b);
		G[u].push_back(tp(v,a,b)); //点 钱 时间 
		G[v].push_back(tp(u,a,b));
	}
	for(int i=1;i<=n;i++)
		scanf("%d%d",&c[i],&d[i]);
	fill(dp[0],dp[0]+54*2465,inf);//全部初始化为inf的骚操作
	dp[1][s]=0;//初始化起点
	//优先队列,按照tuple三个值(从前往后)升序
	priority_queue<tp,vector<tp>,greater<tp>> pq;
	pq.push(tp(0,1,s));
	
	while(!pq.empty()){
		tp cur;
		cur=pq.top();
		pq.pop();
		ll t=get<0>(cur),u=get<1>(cur),w=get<2>(cur);//取tuple值
		if(dp[u][w]>t) continue; //可有可无的判重
		for(auto i:G[u]){//遍历vector
			ll v=get<0>(i),toa=get<1>(i),tob=get<2>(i);
			if(dp[v][w-toa]>t+tob&&w>=toa){//如果有足够钱可达且这样走比原有dp更快
				dp[v][w-toa]=t+tob;
				pq.push(tp(t+tob,v,w-toa));//存入状态
			}
		}
		if(dp[u][min(2450LL,w+c[u])]>t+d[u]){//如果充1次钱后比原有dp更快
			dp[u][min(2450LL,w+c[u])]=t+d[u];
			pq.push(tp(t+d[u],u,min(2450LL,w+c[u])));//存入状态
		}
		//这里只充一次钱,因为接下来会有下一次充钱的状态到来~
	}
	for(int i=2;i<=n;i++){
		ll ans=inf;
		for(int j=0;j<=2450;j++)
			ans=min(ans,dp[i][j]);//取到达时剩下的不同钱中最快的
		printf("%lld\n",ans);
	}
}

你可能感兴趣的:([ACM]【Dijkstra/DP】Atcoder 164 Two Currencies)