分層建圖
首先來一道題,題意是這樣的:
給定一張有向圖(遊戲地圖),一對起點和終點,每個點代表一個城市,你從起點開車到終點,每次在兩個城市間移動需要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]);
}