CCF CSP 201712-4 行车路线

思路:

这题有两种思路:DijkstraSPFA,都可以100分;在求最短路径时,Dijkstra往往是效率比较高的解法,但是本题使用Dijkstra的变形并不能完全贴合题意,因为这种贪心算法遇到“局部最优≠整体最优”的情况就失效了,但是不妨拿来练练手hh;

Dijkstra:
大体框架还是照搬Dijkstra,但是注意使用long long,唯一变形的就是另设一个数组clen[],用来表示第i个结点当前是一段长为clen[i]的短路的终点,因此在下一条长为L的短路接着这条短路时,就可以先减去clen[i]的平方,再加上clen[i]+L的平方了;

SPFA:
1.将大路和小路的路径分开存储,首先利用Ford算法的思想,将小路的距离数组dis_1[i][j]全部更新为走小路的情况下,ij的最短路径;
2.然后利用spfa算法的思想(其实和BFS差不多),当前路口为now,下一个路口i我们可以从2遍历到n,分三种情况前进:(1)从小路到now,现在走大路从nowi;(2)从大路到now,现在走大路从nowi;(3)从大路到now,现在走小路从nowi
3.最后输出min(d_0[n],d_1[n])即可;

Dijkstra代码:

#include<bits/stdc++.h>
#define p_b(a) push_back(a)
typedef long long LL; 
using namespace std;
const int MAX_N=505;
const LL INF=LLONG_MAX-100;
vector<int> node[MAX_N];
LL dis[MAX_N][MAX_N];//两个结点之间的距离 
bool type[MAX_N][MAX_N];//是否是小道 
LL clen[MAX_N];//到点i时连续走的小路长
LL d[MAX_N];//点i的距离 
bool known[MAX_N]={false,true};//点i是否已是最短路 
int n,m;
void dijkstra(){
	fill(d+2,d+MAX_N,INF);
	for(auto e:node[1]){ 
		if(type[1][e]){
			clen[e]=dis[1][e];
			d[e]=clen[e]*clen[e];
		}else d[e]=dis[1][e];
	}
	while(!known[n]){
		int min_n;
		LL min_d=INF;
		for(int i=2;i<=n;i++){
			if((!known[i])&&d[i]<min_d){
				min_d=d[i];
				min_n=i;
			}
		}
		known[min_n]=true;
		for(auto e:node[min_n]){
			if(type[min_n][e]){
				LL c=clen[min_n]+dis[min_n][e];
				LL now=d[min_n]-clen[min_n]*clen[min_n]+c*c;
				if(now<d[e]){
					clen[e]=c;
					d[e]=now;
				}
			}else{
				LL now=d[min_n]+dis[min_n][e];
				if(now<=d[e]){
					clen[e]=0;
					d[e]=now;
				}
			}
		}
	}		
}
int main(){
	cin>>n>>m;
	memset(dis,127,sizeof(dis));
	for(int i=0;i<m;i++){
		int t,a,b;
		LL c;
		cin>>t>>a>>b>>c;
		node[a].p_b(b);
		node[b].p_b(a);
		if(c<dis[a][b]) dis[a][b]=dis[b][a]=c;
		if(t) type[a][b]=type[b][a]=true; 
	}
	dijkstra();
	cout<<d[n];
	return 0;
} 

SPFA代码:

#include<bits/stdc++.h>
using namespace std;
#define rp(i,n) for(int i=0;i<n;i++)
#define rpn(i,n) for(int i=1;i<=n;i++)
#define mem(a,x) memset(a,x,sizeof(a))
typedef long long LL;
const int MAX_N=505;
const LL INF=0x3f3f3f3f;
int n,m;
LL dis_0[MAX_N][MAX_N];//大路 
LL dis_1[MAX_N][MAX_N];//小路
LL d_0[MAX_N],d_1[MAX_N];//前驱分别为大路和小路时,结点i的最短路径 
bool vst[MAX_N];//结点i是否在队列中 
void ford(){//更新小路,使得dis_1[i][j]是从小路走的情况下i到j的最短距离 
	for(int i=1;i<n;i++){
		for(int j=i+1;j<=n;j++){
			rpn(k,n) if(dis_1[i][k]<INF&&dis_1[k][j]<INF)
				dis_1[i][j]=dis_1[j][i]=min(dis_1[i][j],dis_1[i][k]+dis_1[k][j]);
		}
	}				
}
int main(){	
	cin>>n>>m;
	mem(dis_0,INF),mem(dis_1,INF);
	mem(d_0,INF),mem(d_1,INF);
	d_0[1]=d_1[1]=0;
	rp(i,m){
		int t,a,b;
		LL c;
		cin>>t>>a>>b>>c;
		if(t&&c<dis_1[a][b]) dis_1[a][b]=dis_1[b][a]=c;
		else if(!t&&c<dis_0[a][b]) dis_0[a][b]=dis_0[b][a]=c;
	}
	ford();
	queue<int> q;
	q.push(1); 
	while(!q.empty()){//spfa
		int now=q.front();
		q.pop();
		vst[now]=false;
		for(int i=2;i<=n;i++){//从now走到i 
			bool update=false;
			if(dis_0[now][i]<INF){
				LL d=d_0[now]+dis_0[now][i];//之前走大路,现在还是从大路走到i 
				if(d<d_0[i]){
					d_0[i]=d;
					update=true;
				}
				d=d_1[now]+dis_0[now][i];//之前走小路,现在走大路 
				if(d<d_0[i]){
					d_0[i]=d;
					update=true;
				}
			}		
			if(dis_1[now][i]<INF){
				LL d=d_0[now]+dis_1[now][i]*dis_1[now][i];//之前走大路,现在从小路走到i
				if(d<d_1[i]){
					d_1[i]=d;
					update=true;
				} 
			}
			if(update&&!vst[i]){
				q.push(i);
				vst[i]=true;
			}
		}
	}
	cout<<min(d_0[n],d_1[n]);
	return 0;
}
posted @ 2019-11-11 19:34  YuhanのBlog  阅读(174)  评论(0编辑  收藏  举报