【例题收藏】◇例题·I◇ Snuke's Subway Trip
◇例題·I◇?Snuke's Subway Trip
題目來源:Atcoder Regular 061 E題(beta版)?+傳送門+
一、解析
(1)最短路實現
由于在同一家公司的鐵路上移動是不花費的,只有在換乘時會花費1日元。我們可以視換乘的花費為點權——當換乘到不同公司時花費1,同一家公司時花費0。
對于這種點權因情況而變化的題,最常見的做法便是拆點。設節點 u?相連的鐵路由 c1,c2,...,cp?p個公司修建,則將節點u拆分為節點 uc1~ucp,點uci表示到達點u的鐵路是由公司ci修建的。若兩點u,v被公司c修的鐵路相連,則將 uc?與 vc?連接一條權為0的邊——因為在同一家公司的鐵路上移動無花費。
舉個例子:
拆完點后SPFA跑一次最短路,最后答案要除以2。
為什么可以這樣做呢?
如果只通過公司c的鐵路能夠從u到v(中途不轉到其他公司的鐵路),那么我們就能夠從uc花費0日元移動到vc。
如果在節點u要從a公司轉到b公司,我們需要通過 ua->u->ub。如何理解這一過程呢?我們可以把u看成一個站點——你到達u時在公司a的站臺上,然后你需要花費1日元回到站點u,然后通過站點u中轉,再花費1日元到達公司b的站臺。但是我們會發現,題目要求是換乘只需要1日元,而我的程序每換乘一次需要2日元,就會比正確答案大一倍,所以最后要除以2。
可能不太清楚,看個例子
(我覺得我好像講的不大清楚,reader們不懂的可以看文章底的郵箱問我 QwQ)
?(2)?二分圖實現
(其實我這種實現寫TLE了……但是我知道有這種思路,于是寫上了)
對于原圖,我們定義可以通過同一家公司的鐵路到達的點集為一個連通塊,也就是說在同一個連通塊內的所有點互相可以到達,且經過的鐵路是同一家公司的(花費為0),而不同的連通塊的點互相到達需要花費。
由于一個連通塊的點之間花費為0,我們可以把每個連通塊都縮成一個點x,把該連通塊內的點與x連接。然后我們會發現這樣構成的圖形成了一個二分圖,X部為原圖的點,Y部為連通塊縮成的點。再用BFS從原圖的點1(起點)到原圖的點n(終點)搜索一遍求得1到n的最短路,把所得解除以2就是答案。
(同樣的問題)為什么可以這么做呢?
可以把X部看成中轉站,而Y部為鐵路。因為連通塊內的點可以互相到達,我們把從中轉站進入鐵路的花費和從鐵路出到中轉站的花費都設為1,那么同一個連通塊內的兩個點u,v的最小花費路徑則是從u出發經過連通塊內的其他點到達v,花費為0。同樣,從點1到點n的路徑可以看成從1出發經過若干個連通塊到達n。我們可以證明——從1到n的最短路徑經過同一個連通塊的次數不超過1——設最短路徑進入某一個連通塊時點為u,而從該連通塊離開的點為v,一定存在一條花費為0的從u到v的路徑(因為他們在一個連通塊內嘛),而如果從u出發,經過其他連通塊再到達v,則花費一定大于0,因此經過該連通塊的次數一定不超過1。
其實不難發現,u到v的最小花費就是u到v的最短路徑中經過的連通塊的個數(看下面的例子),而且u到v的路徑在二分圖中一定是“u->連通塊1->中轉站1->...->中轉站k->連通塊k->v(k就是答案)”,在二分圖中形成k個“V”形,即經過邊數為 2k?條。所以答案(k)就是經過邊數除以2。
?二、實現
就只講一下那些比較難寫的地方,簡單的步驟就默認大家會了……
(1)最短路拆點 - map儲存編號(ID)
?這一步可以在線處理。對于點u儲存一個map<int,int>ID[u],ID[u][c]表示u的由c公司修的鐵路所拆分的虛擬節點的編號。可以在讀入邊的時候處理——用map自帶的函數count(c)判斷端點u,v的映射(map)中是否存在公司c,如果沒有,則給它賦一個值cnt。我們可以把虛擬節點的編號儲存在原圖節點的后面,即cnt從n+1開始。同時連接u,v關于c公司生成的兩個虛擬節點 ID[u][c],ID[v][c]。
再枚舉1~n的點,對于每一個點i,用迭代器枚舉map。這里是一個技巧,大家可以看一看:
迭代器iterator——STL容器遍歷利器
迭代器相當于一個指針,可以指向特定STL容器的元素。由于STL容器除了vector好像就沒有可以直接訪問、遍歷元素(在不改變原容器的情況下)的容器了,遍歷起來就比較麻煩。我們可以定義一個迭代器,map的格式就像這樣 “map<類型,類型>::iterator”。迭代器本身是一個數據類型,若要遍歷一個 map<int,int> ma ,迭代器就定義為 "map<int,int>::iterator it",用for循環枚舉:“for(map<int,int>::iterator it=ma.begin();it!=ma.end();it++)” 。這里的"it++"是迭代器重定義了"++",指向容器的下一個元素。
map<>這種容器比較特別,它的元素其實是一個pair,其中pair的first是下標,second是下標對應的值。注意用迭代器訪問元素時,迭代器類似于指針,所以訪問first,second時是用指針的"->"而不是結構體的"."。
這里的second就是該虛擬節點對應的ID,直接將該虛擬節點與節點i連邊即可。
for(int i=0;i<n_edg;i++) {int u,v,c;scanf("%d%d%d",&u,&v,&c); //讀邊input[i][0]=u;input[i][1]=v;input[i][2]=c;if(!ID[u].count(c)) ID[u][c]=cnt++; //存虛擬節點idif(!ID[v].count(c)) ID[v][c]=cnt++;lnk[ID[u][c]].push_back(make_pair(ID[v][c],0)); //連接兩個虛擬節點lnk[ID[v][c]].push_back(make_pair(ID[u][c],0)); } for(int i=1;i<=n_pnt;i++) //枚舉原圖點for(map<int,int>::iterator it=ID[i].begin();it!=ID[i].end();it++) //枚舉虛擬點 {int id=it->second; //id是虛擬節點的編號lnk[i].push_back(make_pair(id,1)); //連接原圖點與虛擬節點lnk[id].push_back(make_pair(i,1));}?
(2)二分圖改造 -?原圖與二分圖的轉換(我覺得就是這里TLE了)
先用vector<>鄰接表儲存原圖 fir_lnk,再用map<int,bool> cpn[],cpn[u][k]表示與節點u連接的鐵路是否有k公司修的,即屬于公司k的鐵路連通塊是否包含u。
枚舉點 1~n,再用迭代器枚舉公司,如果節點 i?與公司 j?的連通塊還沒有被枚舉到,則用 BFS(Linker)?遍歷該連通塊,遍歷到一個點后清除該點與連通塊的標記,同時連接點與連通塊(構造二分圖)。
Linker:
void Linker(int start,int edge,int ID) {queue<int> que;que.push(start);while(!que.empty()){int u=que.front();que.pop();lnk[u].push_back(ID); //構造二分圖 lnk[ID].push_back(u);for(int i=0;i<fir_lnk[u].size();i++)if(fir_lnk[u][i].second==edge){int v=fir_lnk[u][i].first;if(!cpn[v][edge]) continue;cpn[v][edge]=false; //清除標記 que.push(v);}} }?
枚舉:
for(int i=1;i<=n_pnt;i++) //枚舉點for(map<int,bool>::iterator it=cpn[i].begin();it!=cpn[i].end();it++) //枚舉連通塊if(it->second)Linker(i,it->first,cnt++); //BFS?
?三、源代碼
(最短路 AC)
1 /*Lucky_Glass*/ 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<iostream> 6 #include<map> 7 #include<vector> 8 #include<queue> 9 using namespace std; 10 const int MAXN=(int)1e5; 11 const int INF=(int)1e9; 12 int n_pnt,n_edg,cnt; 13 map<int,int> ID[MAXN+5]; 14 int input[2*MAXN+5][3]; 15 vector<pair<int,int> > lnk[5*MAXN+5]; 16 int dist[5*MAXN+5]; 17 bool vis[5*MAXN+5]; 18 int main() 19 { 20 scanf("%d%d",&n_pnt,&n_edg); 21 cnt=n_pnt+1; 22 for(int i=0;i<n_edg;i++) 23 { 24 int u,v,c;scanf("%d%d%d",&u,&v,&c); 25 input[i][0]=u;input[i][1]=v;input[i][2]=c; 26 if(!ID[u].count(c)) ID[u][c]=cnt++; 27 if(!ID[v].count(c)) ID[v][c]=cnt++; 28 lnk[ID[u][c]].push_back(make_pair(ID[v][c],0)); 29 lnk[ID[v][c]].push_back(make_pair(ID[u][c],0)); 30 } 31 for(int i=1;i<=n_pnt;i++) 32 for(map<int,int>::iterator it=ID[i].begin();it!=ID[i].end();it++) 33 { 34 int id=it->second; 35 lnk[i].push_back(make_pair(id,1)); 36 lnk[id].push_back(make_pair(i,1)); 37 } 38 fill(dist,dist+5*MAXN+5,INF); 39 dist[1]=0;vis[1]=true; 40 queue<int> que;que.push(1); 41 while(!que.empty()) 42 { 43 int u=que.front();que.pop(); 44 for(int i=0;i<lnk[u].size();i++) 45 { 46 int v=lnk[u][i].first,l=lnk[u][i].second; 47 if(dist[v]>dist[u]+l) 48 { 49 dist[v]=dist[u]+l; 50 if(!vis[v]) 51 vis[v]=true,que.push(v); 52 } 53 } 54 vis[u]=false; 55 } 56 if(dist[n_pnt]==INF) printf("%d\n",-1); 57 else printf("%d\n",dist[n_pnt]/2); 58 return 0; 59 } View Code?
(二分圖 TLE)
1 /*Lucky_Glass*/ 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 #include<queue> 7 #include<map> 8 using namespace std; 9 int n_pnt,n_edg,cnt; 10 const int MAXN=int(3e5); 11 vector<int> lnk[MAXN+5]; 12 vector<pair<int,int> > fir_lnk[int(1e5)+5]; 13 map<int,bool> cpn[int(1e5)+5]; 14 bool vis[MAXN+5],fir_vis[int(1e5)+5]; 15 void Linker(int start,int edge,int ID) 16 { 17 queue<int> que; 18 que.push(start); 19 while(!que.empty()) 20 { 21 int u=que.front();que.pop(); 22 lnk[u].push_back(ID); 23 lnk[ID].push_back(u); 24 for(int i=0;i<fir_lnk[u].size();i++) 25 if(fir_lnk[u][i].second==edge) 26 { 27 int v=fir_lnk[u][i].first; 28 if(!cpn[v][edge]) continue; 29 cpn[v][edge]=false; 30 que.push(v); 31 } 32 } 33 } 34 int main() 35 { 36 scanf("%d%d",&n_pnt,&n_edg); 37 cnt=n_pnt+1; 38 for(int i=0;i<n_edg;i++) 39 { 40 int u,v,c; 41 scanf("%d%d%d",&u,&v,&c); 42 fir_lnk[u].push_back(make_pair(v,c)); 43 fir_lnk[v].push_back(make_pair(u,c)); 44 cpn[u][c]=cpn[v][c]=true; 45 } 46 for(int i=1;i<=n_pnt;i++) 47 for(map<int,bool>::iterator it=cpn[i].begin();it!=cpn[i].end();it++) 48 if(it->second) 49 Linker(i,it->first,cnt++); 50 queue<pair<int,int> > que; 51 que.push(make_pair(1,0)); 52 while(!que.empty()) 53 { 54 pair<int,int> fro=que.front();que.pop(); 55 int u=fro.first,stp=fro.second; 56 for(int i=0;i<lnk[u].size();i++) 57 { 58 int v=lnk[u][i],fstp=stp+1; 59 if(vis[v]) continue; 60 vis[v]=true; 61 if(v==n_pnt) {printf("%d\n",fstp/2);return 0;} 62 que.push(make_pair(v,fstp)); 63 } 64 } 65 printf("-1\n"); 66 return 0; 67 } View CodeThe End
Thanks for reading!
-?Lucky_Glass
(Tab:如果我有沒講清楚的地方可以直接在郵箱lucky_glass@foxmail.com email我,在周末我會盡量解答并完善博客~)
?
?
轉載于:https://www.cnblogs.com/LuckyGlass-blog/p/9330160.html
總結
以上是生活随笔為你收集整理的【例题收藏】◇例题·I◇ Snuke's Subway Trip的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何升级php到最新版本_如何将PHP升
- 下一篇: 使用python抓取美团商家信息