分层建图
首先来一道题,题意是这样的:
给定一张有向图(游戏地图),一对起点和终点,每个点代表一个城市,你从起点开车到终点,每次在两个城市间移动需要1h。每个点(城市)都有五种可能的情况:
1.该点没有任何道具;
2.该点有阻碍物需要停止1h;
3.到达该点时游戏失败(保证起点和终点不为3);
4.该点有氮气,接下来连续两次移动速度加倍(倍数不可叠加,次数可以);
5.该点有沙子,接下来连续两次移动速度减半(同上);
求从起点到终点最快用时,如果不能到达终点输出-1.
分析题目,单源最短路径,很显然就该用 SPFA dijkstra算法。对于各类点,遇到1不管,2的话将该点的出边权值(用时)加一,3的话删除连接这个点的所有边。
但是,4和5两种情况怎么处理?怎么实现同一条边权值(用时)加倍和减半的操作,怎么让1、0.5、2三种边权同时存在于一条边上呢?
换个想法,与其纠结在一条边上进行的多次修改,不如把所有可能的权值都建立一条边,也就是说,分层建图。
(当然,这并不是什么算法,只是一种建图的技巧/思想。
就这题而言,可以将图分为三层,分别叫做G1G2G3,G1的边权都为1,G2都为0.5,G3都为2,初始在G1层的起点。对于每一个第四类点(氮气),我们可以向G3的同一点连一条无权边(权值为0),然后对于所有和该点距离在2以内的G3层点向G1连一条无权边,如图所示其中G1.1为氮气:
(今天刚学Graphiviz,画的太丑,暂时凑合着看吧)
同理,遇到第五类点作相同处理,这题就变成裸的dijkstra啦。
下面来做道题目体会一下:
给出 个点, 条边的无向图,一对起点和终点,给你 次将当前边权变为 的机会,求从起点到终点的最小代价。
由于 比较小,我们可以用分层建图的思想建出 层图,分别表示在各点已经使用了 次机会的状态,各层之间转移的边权为 ,建完之后跑dij即可。
AC代码:
#include <bits/stdc++.h>
#define PII pair<int,int>
#define N 10010
#define ll long long
using namespace std;
bool vis[N*11];//用0~n-1,n~2n-1...kn~(k+1)n-1表示k+1层的点
vector<PII> G[N];//由于各层的连通情况都相同,只需对初始层建图,各层用对n取模访问
int n,m,k,s,t,d[N*11];
void dijkstra(int s)
{
priority_queue<PII,vector<PII>,greater<PII> > pq;
pq.push(make_pair(0,s));
d[s]=0;
while(!pq.empty())
{
int u=pq.top().second;pq.pop();
if(vis[u])continue;vis[u]=1;
for(int it=0;it<G[u%n].size();it++)//QAQ,bzoj不能for(auto:G)
{
PII i=G[u%n][it];
int v=i.second+u-u%n;
if(d[u]+i.first<d[v])//正常松弛
{
d[v]=d[u]+i.first;
pq.push(make_pair(d[v],v));
}
if(v+n<(k+1)*n&&d[v+n]>d[u])//到下一层
{
d[v+n]=d[u];
pq.push(make_pair(d[v+n],v+n));
}
}
}
}
int main()
{
scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
G[u].push_back(make_pair(w,v));
G[v].push_back(make_pair(w,u));
}
memset(d,0x3f,sizeof(d));
dijkstra(s);
printf("%d",d[k*n+t]);
}