1724:ROADS,考點:深搜剪枝
原題:http://bailian.openjudge.cn/practice/1724/
描述
N cities named with numbers 1 ... N are connected with one-way roads. Each road has two parameters associated with it : the road length and the toll that needs to be paid for the road (expressed in the number of coins).
Bob and Alice used to live in the city 1. After noticing that Alice was cheating in the card game they liked to play, Bob broke up with her and decided to move away - to the city N. He wants to get there as quickly as possible, but he is short on cash.
We want to help Bob to findthe shortest path
(那麼長的英文我也懶得看)
輸入
The first line of the input contains the integer K, 0 <= K <= 10000, maximum number of coins that Bob can spend on his way.
The second line contains the integer N, 2 <= N <= 100, the total number of cities.
The third line contains the integer R, 1 <= R <= 10000, the total number of roads.
Each of the following R lines describes one road by specifying integers S, D, L and T separated by single blank characters :
- S is the source city, 1 <= S <= N
- D is the destination city, 1 <= D <= N
- L is the road length, 1 <= L <= 100
- T is the toll (expressed in the number of coins), 0 <= T <=100
Notice that different roads may have the same source and destination cities.
輸出
The first and the only line of the output should contain the total length of the shortest path from the city 1 to the city N whose total toll is less than or equal K coins.
If such path does not exist, only number -1 should be written to the output.
樣例輸入
5 6 7 1 2 2 3 2 4 3 3 3 4 2 4 1 3 4 1 4 6 2 1 3 5 2 0 5 4 3 2
樣例輸出
11
解法
思路:深搜,遞迴的邊界是第N個城市,由於每條路都是單向的,不用擔心同一條路走回來的問題。但是資料大,要剪枝。
- 已經經過的城市就不要再去了(因為無後效性,每個城市的所有路徑情況與什麼時候經過這個城市無關),
- 最優剪枝:如果已經大於等於最短長度,就可以不再搜尋了
- 合理性剪枝:如果花費已經超過擁有的K元,也不用搜了
- 最後記住需要回溯。以及遞迴邊界要記得return(不要太high了,遞迴邊界都不return)
坑:
- 解題思路方面:注意題目中是K元能到達的最小花費的最短路,可能會有更短的路徑但是花費超過K。不能直接算最短路徑的最小花費與K進行比較來判斷。
- 記憶體方面:這道題資料挺大的,無論是頂點還是每個頂點對應的邊集都應該用vector來存,不然會MLE。
- 時間方面:原來在遞迴中,中間結果作為引數以及回溯位置的設定會影響時間。
第一個版本,把中間結果作為了引數,回溯寫在了遞迴的最後,超時了。
#include <iostream> #include <vector> #include <cstring> #define INF 1<<30 using namespace std; int K, N, R; struct edge { int len; int pay; int end; edge(int e,int l,int p):end(e),len(l),pay(p){} }; int minpay; int minlen; vector<vector<edge> >V; int minL[101][10005]; bool visited[101]; void dfs(int city,int len,int pay) { if (city == N) { if (len < minlen) minlen = len; if (len == minlen) { if (pay < minpay) minpay = pay; } return; } if (minL[city][pay] == -1) minL[city][pay] = len; visited[city] = true; int edgenum = V[city].size(); for (int i = 0; i < edgenum; i++) { edge temp = V[city][i]; if (visited[temp.end])//重複的剪枝 continue; if (len + temp.len >= minlen)//最優剪枝 continue; if (pay + temp.pay > K)//合理性剪枝 continue; if (minL[temp.end][pay + temp.pay] != -1 && len + temp.len >= minL[temp.end][pay + temp.pay]) continue; dfs(temp.end, len + temp.len, pay + temp.pay); } visited[city] = false;//回溯 } int main() { memset(minL, -1, sizeof(minL)); memset(visited, 0, sizeof(visited)); cin >> K >> N >> R; V.resize(N + 1); for (int i = 0; i < R; i++) { int s, e, l, p; cin >> s >> e >> l >> p; if(s!=e) V[s].push_back(edge(e, l, p)); } minpay = INF; minlen = INF; dfs(1, 0, 0); if (minlen == INF) cout << -1 << endl; else cout << minlen << endl; return 0; }
第二個版本,把中間結果作為全域性變數,需要進行回溯,回溯的位置在迴圈內部。這個版本的程式碼AC了
1 #include <iostream> 2 #include <vector> 3 #include <cstring> 4 #define INF 1<<30 5 using namespace std; 6 int K, N, R; 7 struct edge { 8 int len; 9 int pay; 10 int end; 11 edge(int e,int l,int p):end(e),len(l),pay(p){} 12 }; 13 int minpay; 14 int minlen; 15 vector<vector<edge> >V; 16 int minL[101][10005]; 17 bool visited[101]; 18 int totallen, totalpay; 19 void dfs(int city) { 20 if (city == N) { 21 if (totallen < minlen) 22 minlen = totallen; 23 if (totallen == minlen) { 24 if (totalpay < minpay) 25 minpay = totalpay; 26 } 27 return; 28 } 29 int edgenum = V[city].size(); 30 for (int i = 0; i < edgenum; i++) { 31 edge temp = V[city][i]; 32 if (visited[temp.end])//重複的剪枝 33 continue; 34 if (totallen + temp.len >= minlen)//最優剪枝 35 continue; 36 if (totalpay + temp.pay > K)//合理性剪枝 37 continue; 38 if (minL[temp.end][totalpay + temp.pay] != -1 && totallen + temp.len >= minL[temp.end][totalpay + temp.pay]) 39 continue; 40 totallen += temp.len; 41 totalpay += temp.pay; 42 minL[temp.end][totalpay] = totallen; 43 visited[temp.end] = true; 44 dfs(temp.end); 45 visited[temp.end] = false; 46 totallen -= temp.len; 47 totalpay -= temp.pay; 48 } 49 } 50 int main() 51 { 52 memset(minL, -1, sizeof(minL)); 53 memset(visited, 0, sizeof(visited)); 54 cin >> K >> N >> R; 55 V.resize(N + 1); 56 for (int i = 0; i < R; i++) { 57 int s, e, l, p; 58 cin >> s >> e >> l >> p; 59 if(s!=e) 60 V[s].push_back(edge(e, l, p)); 61 } 62 minpay = INF; 63 minlen = INF; 64 totallen = 0; 65 totalpay = 0; 66 dfs(1); 67 if (minlen == INF) 68 cout << -1 << endl; 69 else 70 cout << minlen << endl; 71 return 0; 72 }