Mr. Rito Post Office
Time Limit : 8 sec, Memory Limit : 65536 KBProblem D: Mr. Rito Post Office
あなたは離島の郵便局に勤めるプログラマである.あなたの住んでいる地域は,複数の島々からなる.各島には一つ以上の港町がある.それらに加えて他の町や村があるかもしない.ある島から別の島に向かうためには船を使わなければならない.一つの島の中を回るには陸路が使えるが,海路を利用した方が速いこともある.
近年行われた郵便局の民営化をきっかけに,経費削減に向けて郵便配達員の人員整理が全国的に行われた.離島の郵便局もその例外ではなく,結果として郵便配達員は利藤さんただ一人となってしまった.その郵便局が集配を担当する地域は非常に広いため,一人で集配するのは大変な作業である.なので,どのようにすれば効率的に集配を行えるのか,利藤さんがあなたに助けを求めてきた.
あなたの仕事は,利藤さんがたどるべき町や村の集配順序が与えられたときに,最短巡回経路を求めるプログラムを書くことである.
利藤さんは,決して指定された順序以外で集配業務を行うことができない.しかし,ある町や村から別の町や村へ移動する際に,他の町や村を経由して移動することは許可されている.また,利藤さんは島々を巡るための一隻の船を持っている.
例えば,町A,町B,村Cという集配順序が与えられた場合,町Aから町Bへ向かう際にどの町や村を経由してもかまわない.このとき,村Cを経由しても構わないが,集配順序を守るためには,一度町Bに行って集配をおこなってから,改めて村Cを訪れて集配をおこなわなければならない.また,町Aから海路を用いて町Bに向かい,町Bから陸路を用いて村Cに向かった場合,船を町Bに置いたままになる.したがって,次に海路を使いたい場合は町Bに戻る必要がある.
一つの町や村において複数回集配をしなければならない場合もある.たとえば,町A,村B,町C,村Bという集配順序が与えられるかもしれない.このとき,町Aから村Bをたどらずに町Cに向かった場合は,町Cでいきなり集配をおこなうことはできない.最初の村Bでの集配が終わっていないからである.町Cで集配を済ませた後で村Bを訪れて集配しても,一回目の村Bの集配を終わらせたことにはならない.
利藤さんは,はじめに必ずある港町に船とともにいる.利藤さんはベテランであるため,移動時間以外の集配作業にかかる時間は無視してよい.また,最後の町または村での集配業務が完了するまでの時間だけが問題であり,船をもとの位置に戻して郵便局に帰るまでの時間は考慮しなくてよい.
Input
入力は複数のデータセットから構成される.各データセットの形式は次に示すとおりである.
N M
x1 y1 t1 sl1
x2 y2 t2 sl2
...
xM yM tM slM
R
z1 z2 ... zR
データセットの中の入力項目は,すべて非負の整数である.行中の入力項目の区切りは空白 1 個である.
最初の行は,陸路及び海路網の大きさを規定する.
N (2 ≤ N ≤ 200) は,町または村の数である. それぞれの町または村には,1 から N までの固有の番号を割り当てる. M (1 ≤ M ≤ 10000) は,陸路と海路の合計本数である.
2 行目から 1 + M 行目は,陸路または海路の記述である. xi と yi (1 ≤ xi, yi ≤ N) は両端の町または村の番号を表す. ti (1 ≤ ti ≤ 1000) はその陸路または海路の移動時間を表す. sli は ‘L’ または ‘S’ のいずれかであり,Lは陸路を,Sは海路をそれぞれ表す.
ある2つの町や村を直接結ぶ陸路または海路が2本以上存在することがある. それぞれの陸路または海路は双方向であり,すなわちどちらの向きにも移動できる.
M + 2 行目の R (1 ≤ R ≤ 1000)は,利藤さんが担当する集配先の数を表す. M + 3 行目には,集配先の町や村の番号 zi (1 ≤ zi ≤ N) が集配順に R 個並んでいる.
初期状態では利藤さんと船はともに港町 z1 に存在する. 初期状態から集配先の町や村へは,必ず何らかの方法で移動することができる.
入力の終わりは,空白で区切られた2つの0を含む1行で示される.
Output
入力の各データセットに対して,与えられた集配順序で利藤さんが町と村を巡回するために必要な最短移動時間を求め,1行に出力せよ.
Sample Input
3 3 1 2 5 L 1 2 7 S 2 3 11 S 3 1 2 3 5 5 1 2 15 L 2 3 10 L 4 5 7 L 1 3 30 S 3 4 100 S 5 1 3 5 4 1 0 0
Output for the Sample Input
18 269
题目含义及思路参考于hacks博主的博客,具体如下:
题意:全岛有一些镇子通过水路和旱路相连,走水路必须要用船,在X处下船了船就停在X处。而且岛上只有一条船,下次想走水路还是得回到X处才行;两个镇子之间可能有两条以上的水路或旱路;邮递员必须按照清单上的镇子顺序送快递(镇子可能重复,并且对于重复的镇子不允许一次性处理,比如ABCB的话B一定要按顺序走两次才行)。
思路:
最短路
先单独考虑只走水路或旱路的情况,用warshall_floyd求出任意两点间的最短路。
然后定义 dp[i][k] := 已经去了第i个镇子后,船停在第k个镇子里的状态下的最短路。
然后ijk三重循环更新dp,其中递推公式思路:
在推导ik的时候,定义一个中间状态j表示先从i-1走旱路到j,然后从j走水路去k,最后从k走旱路去i,于是就把船扔在了k。如果j==k的时候就不需要绕圈子了。
代码:
#include<iostream> #include<algorithm> using namespace std; #define INF 1e8 const int V_MAX =256; const int R_MAX =1024; int dl[V_MAX][V_MAX];//旱路最短 int ds[V_MAX][V_MAX];//水路最短 int z[R_MAX];//存储要去的镇子 int dp[R_MAX][V_MAX];//邮递员去了第i个镇子,船停留在第k个镇子的最短路 int V; int main() { int M;while (cin >> V >> M&&V||M) { for (int i = 0;i < V;i++) { for (int j = 0;j < V;j++) { if (i == j) { dl[i][j] = 0; ds[i][j] = 0; } else { dl[i][j] = INF;ds[i][j] = INF; }/////!!!!!!!!!!!!!!不要越界 } } for (int i = 0;i < M;i++) { int x, y, t;char c; cin >> x >> y>>t>>c; x--;y--;////!!!!!!!!!!!!!!!!! if (c == 'L') { dl[x][y] = min(dl[x][y], t); dl[y][x] = dl[x][y]; } else { ds[x][y] = min(ds[x][y],t); ds[y][x] = ds[x][y]; } } int R; cin>> R; for(int i = 0;i < R;i++) { cin >> z[i]; z[i]--; } //floyd算法: for (int k = 0;k < V;k++) { for (int i = 0;i < V;i++) { for (int j = 0;j < V;j++) { dl[i][j] = min(dl[i][k] + dl[k][j], dl[i][j]); ds[i][j] = min(ds[i][k] + ds[k][j], ds[i][j]); } } } //end of floyd,至此找到了镇子与镇子之间只走水路或只走旱路的最短距离 for (int i = 0; i < R; ++i) { for (int j = 0; j < V; ++j) { dp[i][j] = INF; } } for (int i = 0;i < V;i++) {//dp意义:代表去过第i个镇子(i不是代表编号,只是邮递员去的第i个镇子),船在第k个镇子的所走的最短路程(当前的邮递员在第i个镇子) dp[0][i] = ds[z[0]][i]+dl[i][z[0]]; } for (int i = 1;i < R;i++) { for (int j = 0;j < V;j++) { for (int k = 0;k < V;k++) { if (j != k) { dp[i][k] = min(dp[i][k], dp[i - 1][j] + dl[z[i - 1]][j] + ds[j][k] + dl[k][z[i]]); } else{//说明不用走水路 dp[i][k] = min(dp[i][k],dp[i-1][k]+dl[z[i-1]][z[i]]); } } } } cout << *min_element(dp[R-1],dp[R-1]+V) << endl; } return 0; }