Daimayuan Online Judge CCPC Harbin 2021 G, Damaged Bicycle(状压dp+概率dp+最短路)

题目链接:CCPC Harbin 2021 G, Damaged Bicycle - 题目 - Daimayuan Online Judge

视频题解:【算法camp】【每日一题】Namomo Spring Camp Div1 第13天题解(状压dp、最短路)dls讲题!!_哔哩哔哩_bilibili

 

个人觉得挺有难度的一道题,考察知识点非常多。

需要注意的是,当遇到一辆车坏了,可以选择直接走去终点,也可以尝试走到下一个没有去过的单车位找单车。这里涉及到各种走法的距离,所以需要预处理起点和所有单车位的单源最短路。

至于dp的过程,由于涉及到很多边界问题,最好用记忆化dfs的写法,不容易出错。dp式子就涉及到单车坏了的时候,上述的多种抉择:1.直接走去终点。2.找其他单车。

另外,再说一下dp初始化的问题。可以把起点看成一个必定坏车的单车位(代码中把起点当成了第k+1个单车位。a[k+1]=1,p[k+1]=100),然后从起点开始dfs。而在dfs过程中,我们找下一个单车位的时候,不用遍历到k+1,这样就保证了不会重复回到起点。

代码如下:

#include <bits/stdc++.h>
using namespace std;
// #define int long long
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
const int inf =0x3f3f3f3f;
const int N = 1e5+5;
struct node{int to,w;};
int t,r,n,m,k; //走速,骑速,点数,边数,单车数
vector<node> g[N]; //图
int a[N],d[20][N]; //a[i]记录i号单车位的位置。d[i][j]表示:从i号单车位开始,到j位置的最短距离
double p[N],dp[1<<20][20];
priority_queue<node> q;
inline bool operator < (node a,node b){return a.w>b.w;} //用于优先队列排序

inline void dij(int s){
	memset(d[s],inf,sizeof(d[s]));
	q.push({a[s],0}); d[s][a[s]]=0;
	while(q.size()){
		auto P=q.top(); q.pop();
		int u=P.to;
		for(auto x:g[u]){
			int v=x.to, w=x.w;
			if(d[s][v] > d[s][u]+w)
				d[s][v] = d[s][u]+w, q.push({v,d[s][v]});
		}
	}
}
double dfs(int sta,int u){
	if(dp[sta][u]) return dp[sta][u];
	double tmp=1.0*p[u]*d[u][n]/t+(1-p[u])*d[u][n]/r; //如果车坏了,就直接走去终点,否则骑车去终点
	FOR(i,1,k){
		if(sta&(1<<(i-1))) continue; //已经去过这个单车位,这个单车已知是坏的,跳过
		tmp=min(tmp,1.0*(1-p[u])*d[u][n]/r+p[u]*(1.0*d[u][a[i]]/t+dfs(sta|(1<<(i-1)),i)));
		//分两种情况:1.单车是好的,直接去终点。 2.单车坏了,走去i号单车位,再去终点
	}
	return dp[sta][u]=tmp;
}
signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>t>>r>>n>>m; int x,y,w;
	FOR(i,1,m){ //读入图的信息
		cin>>x>>y>>w;
		g[x].push_back({y,w}); g[y].push_back({x,w});
	}
	cin>>k; //读入所有共享单车位置和坏的概率信息
	FOR(i,1,k) cin>>a[i]>>p[i], p[i]/=100.0, dij(i);
	a[k+1]=1;
	dij(k+1);  p[k+1]=1;
	if(d[k+1][n]==inf) {puts("-1"); return 0;}
	printf("%.6f\n",dfs(0,k+1)); //记忆化dfs,等效于跑一个状压dp
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值