PAT-ADVANCED1034——Public Bike Management
題目描述:
題目翻譯:
1018 公共自行車管理
杭州的公共自行車服務給來自世界各地的遊客帶來了便利。人們可以在任意一個公共自行車站點租借自行車並在任意一個其他公共自行車站點歸還自行車。公共自行車管理中心(PBMC)實時觀察者所有公共自行車站點的自行車數量。如果一個公共自行車站點的公共自行車數量是該站點最大可停放量的一半,我們認為該站點處於“完美”狀態。如果一個公共自行車站點的公共自行車數量為空或者滿了,PBMC會增加或減少該站點的公共自行車數量使其處於“完美”狀態。同時,在PBMC到該站點的路徑上的所有公共自行車站點也會被調整至“完美”狀態。
當一個公共自行車站點發生了問題,PBMC會選擇到達那個站點的最短路徑。如果有多條最短路徑,PBMC會選擇那條需要提供最少公共自行車數量的路徑。
上述圖形展示了一個例子。點代表公共自行車站點,邊代表路徑。邊上的數字代表這條路徑所需花費的時間。點中的數字代表當前該點擁有的公共自行車數量。每個站點最大的公共自行車容量都是10。為了解決S3中發生的問題,我們有兩條最短路徑:
1.PBMC -> S1 -> S3。在這條路徑中PBMC必須提供4輛公共自行車,因為我們可以把S1站點中的一輛放在S3站點,再額外提供4輛,於是每個站點都處於了“完美”狀態。
2.PBMC -> S2 -> S3。這條路徑和第1條路徑花費的時間相同,但是PBMC只需提供3輛公共自行車,因此PBMC會選擇這條路徑。
輸入格式:
每個輸入檔案包含一個測試用例。在每個測試用例中,第一行包含4個數字:Cmax(<= 100),是一個偶數,代表每個站點的最大公共自行車容量;N(<= 500),總站點數;Sp,發生問題的站點編號(每個站點編號為1 ~ N,PBMC的編號為0);M,路徑數量。第二行包含了N個非負數字Ci(i = 1, ..., N),代表每個站點目前擁有的公共自行車數量。接下來的M行,每行包含3個數字Si、Sj和Tij,表示從站點Si到Sj需要花費時間Tij。一行中的所有數字都由一個空格隔開。
輸出格式:
對每個測試用例,在一行中列印你的結果。首先打印出PBMC需要提供的公共自行車數量。然後隔一個空格,以如下形式輸出路徑:0−>S1−>⋯−>Sp。最後再隔一個空格,在調整Sp站點至完美狀態後,我們需要帶回到PBMC的公共自行車數量。
注意如果路徑不唯一,輸出那條我們需要帶回到PBMC的公共自行車數量最少的路徑。測試資料保證這樣的路徑是唯一的。
輸入樣例:
10 3 3 5
6 7 0
0 1 1
0 2 1
0 3 3
1 3 1
2 3 1
輸出樣例:
3 0->2->3 0
知識點:Dijkstra演算法、Bellman-Ford演算法、SPFA演算法、深度優先遍歷
思路一:Dijkstra演算法+深度優先遍歷(鄰接表實現)
本題是常規的最短路徑問題,難點在於第二條件和第三條件的確定。
一開始,我以為只需要計算路徑中所有公共自行車站點已有的公共自行車數量,再和其滿容量的一半作差。如果是正數,那麼我們就需要回收相應數量的公共自行車。如果是負數,我們就需要提供相應數量的公共自行車。如果是0,我們既不需要提供也不需要回收自行車。這其實是不對的,考慮下述情況:(數字代表站點已有的公共自行車數量,每個站點的滿容量假設為10)
3->2->10
按我剛剛的邏輯,10 + 3 + 2 == 15,我們既不需要提供也不需要回收自行車。但事實上題目的要求是,當我們第一次走到3這個節點時,我們就需要給其提供2輛公共自行車。同理,當我們第一次走到2這個節點時,我們就需要給其提供3輛自行車。因此我們需要提供的自行車數量總數是5。而對於10這個節點,我們需要回收5輛自行車。因此這種情況應該是提供5輛自行車,又回收5輛自行車。
因此我們求得第二第三條件的邏輯應該這樣:
(1)設定第二條件,提供自行車數量的最優值初值optValue1 = INF,這裡INF為我自定義的一個無窮大數。設定第三條件,回收自行車數量的最優值初值optValue2 = INF。
(2)在深度優先遍歷到達遞迴終點的時候,我們需要按如下步驟計算這條路徑需要提供和回收的自行車數量。
a:設需要提供的自行車數量provide初值為0,需要回收的自行車數量初值recycle也為0。
b:如果當前節點擁有的自行車數量大於或等於最大容量的一半,那麼當前節點需要回收對應差值的自行車數量,recycle加上相應的值。
c:如果當前節點擁有的自行車數量小於最大容量的一半,我們需要判斷我們當前回收來的自行車數量能否填補這個空缺,即recycle是否大於等於其差值。
c-1:如果我們當前回收來的自行車數量能夠填補這個空缺,那麼只需要recycle值減去相應的值即可。
c-2:如果我們當前回收來的自行車數量無法填補這個空缺,那麼就需要我們額外提供自行車,該數量為所有回收來的自行車都填補這個空缺後,填補該空缺還需要的自行車數量。還有,由於recycle全部用於填補了空缺,其值需要置0。
(3)如果當前路徑的provide小於optValue1,那麼我們以該路徑作為最優路徑,同時更新optValue1和optValue2的值。
(4)如果當前路徑的provide等於optValue1,且當前路徑的recycle值小於optValue2,我們也以該路徑作為最優路徑,同時更新optValue2的值。
正是由於第二條件和第三條件與整條路徑相關,因此我們只能通過Dijkstra演算法+深度優先遍歷的方式來解決這個問題,而不能單純地用Dijkstra演算法解決此問題。
時間複雜度是O(N ^ 2)。空間複雜度是O(N + M)。
C++程式碼:
#include<iostream>
#include<vector>
using namespace std;
struct node {
int v; //節點編號
int time; //邊權
node(int _v, int _time) : v(_v), time(_time) {}; //建構函式
};
int Cmax; //每個站點的最大容量
int N; //站點數量
int Sp; //目的地
int M; //道路數量
int INF = 1000000000; //定義無窮大數
int C[502] = {0}; //存放每個站點當前自行車數量
vector<node> graph[502]; //無向圖
bool visited[502] = {false}; //標記陣列
int d[502]; //記錄最短路徑長度
vector<int> pre[502]; //記錄前一個節點
vector<int> tempPath;
vector<int> path;
int optValue1 = INF; //記錄需要提供的公共自行車數量,最小為最優
int optValue2 = INF; //記錄需要回收的公共自行車數量,最小為最優
void dijkstra(int s);
void dfs(int nowVisit);
int main() {
cin >> Cmax >> N >> Sp >> M;
int Ci;
for(int i = 1; i <= N; i++) {
cin >> Ci;
C[i] = Ci;
}
int Si, Sj, Tij;
for(int i = 0; i < M; i++) {
cin >> Si >> Sj >> Tij;
graph[Si].push_back(node(Sj, Tij));
graph[Sj].push_back(node(Si, Tij));
}
dijkstra(0); //PBMC所在的位置0節點就是起點
dfs(Sp);
cout << optValue1 << " ";
for(int i = path.size() - 1; i >= 0; i--) {
cout << path[i];
if(i != 0) {
cout << "->";
}
}
cout << " " << optValue2 << endl;
return 0;
}
void dijkstra(int s) {
for(int i = 0; i <= N; i++) {
d[i] = INF;
}
d[s] = 0;
for(int i = 0; i <= N; i++) {
int u = -1, min = INF;
for(int j = 0; j <= N; j++) {
if(!visited[j] && d[j] < min) { //j需要是未標記的元素
min = d[j];
u = j;
}
}
if(u == -1) {
return;
}
visited[u] = true;
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v;
int time = graph[u][j].time;
if(!visited[v]) {
if(time + d[u] < d[v]) {
d[v] = time + d[u];
pre[v].clear();
pre[v].push_back(u);
} else if(time + d[u] == d[v]) {
pre[v].push_back(u);
}
}
}
}
}
void dfs(int nowVisit) {
if(nowVisit == 0) {
tempPath.push_back(nowVisit);
int provide = 0, recycle = 0; //provide需要提供的公共自行車數量,recycle需要回收的公共自行車數量
for(int i = tempPath.size() - 2; i >= 0; i--){
if(C[tempPath[i]] - Cmax / 2 >= 0){
recycle += C[tempPath[i]] - Cmax / 2;
}else{
if(recycle >= Cmax / 2 - C[tempPath[i]]){
recycle -= Cmax / 2 - C[tempPath[i]];
}else{
provide += Cmax / 2 - C[tempPath[i]] - recycle;
recycle = 0;
}
}
}
if(provide < optValue1){
optValue1 = provide;
optValue2 = recycle; //此處也要更新optValue2的值
path = tempPath;
}else if(provide == optValue1 && recycle < optValue2){
optValue2 = recycle;
path = tempPath;
}
tempPath.pop_back();
return; //這裡是遞迴終止條件,直接返回
}
tempPath.push_back(nowVisit);
for(int i = 0; i < pre[nowVisit].size(); i++) {
dfs(pre[nowVisit][i]);
}
tempPath.pop_back();
}
C++解題報告:
思路二:Bellman-Ford演算法+深度優先遍歷(鄰接表實現)
時間複雜度是O(M * N)。空間複雜度是O(N + M)。
C++程式碼:
#include<iostream>
#include<vector>
#include<set>
using namespace std;
struct node {
int v; //節點編號
int time; //邊權值
node(int _v, int _time) : v(_v), time(_time) {}; //建構函式
};
int Cmax; //每站最大容量
int N; //總站數
int Sp; //目的地
int M; //道路總數
int INF = 1000000000; //無窮大數
int C[502]; //每站現有公共自行車數量
vector<node> graph[502]; //無向圖
int d[502]; //儲存最短路徑長度
set<int> pre[502]; //記錄前一節點
vector<int> path;
vector<int> tempPath;
int optValue1 = INF;
int optValue2 = INF;
bool bellmanFord(int s);
void dfs(int nowVisit);
int main() {
cin >> Cmax >> N >> Sp >> M;
for(int i = 1; i <= N; i++) {
cin >> C[i];
}
int Si, Sj, Tij;
for(int i = 0; i < M; i++) {
cin >> Si >> Sj >> Tij;
graph[Si].push_back(node(Sj, Tij));
graph[Sj].push_back(node(Si, Tij));
}
bellmanFord(0);
dfs(Sp);
cout << optValue1 << " ";
for(int i = path.size() - 1; i >= 0; i--){
cout << path[i];
if(i != 0){
cout << "->";
}
}
cout << " " << optValue2 << endl;
return 0;
}
bool bellmanFord(int s) {
for(int i = 0; i <= N; i++) {
d[i] = INF;
}
d[s] = 0;
for(int i = 0; i < N; i++) {
for(int u = 0; u <= N; u++) {
for(int k = 0; k < graph[u].size(); k++) {
int v = graph[u][k].v;
int time = graph[u][k].time;
if(d[u] + time < d[v]) {
d[v] = d[u] + time;
pre[v].clear();
pre[v].insert(u);
} else if(d[u] + time == d[v]) {
pre[v].insert(u);
}
}
}
}
for(int u = 0; u <= N; u++) {
for(int k = 0; k < graph[u].size(); k++) {
int v = graph[u][k].v;
int time = graph[u][k].time;
if(d[u] + time < d[v]) {
return false;
}
}
}
return true;
}
void dfs(int nowVisit){
if(nowVisit == 0){
tempPath.push_back(nowVisit);
int provide = 0, recycle = 0;
for(int i = tempPath.size() - 2; i >= 0; i--){
if(C[tempPath[i]] - Cmax / 2 >= 0){
recycle += C[tempPath[i]] - Cmax / 2;
}else{
if(recycle >= -C[tempPath[i]] + Cmax / 2){
recycle -= -C[tempPath[i]] + Cmax / 2;
}else{
provide += -C[tempPath[i]] + Cmax / 2 - recycle;
recycle = 0;
}
}
}
if(provide < optValue1){
optValue1 = provide;
optValue2 = recycle;
path = tempPath;
}else if(provide = optValue1 && recycle < optValue2){
optValue2 = recycle;
path = tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(nowVisit);
set<int>::iterator it;
for(it = pre[nowVisit].begin(); it != pre[nowVisit].end(); it++){
dfs(*it);
}
tempPath.pop_back();
}
C++解題報告:
思路三:SPFA演算法+深度優先遍歷
期望時間複雜度是O(kN),其中k是一個常數,在很多情況下k不超過2,可見這個演算法異常高效,並且經常性地優於堆優化的Dijkstra演算法。空間複雜度是O(N + M)。
C++程式碼:
#include<iostream>
#include<vector>
#include<set>
#include<queue>
using namespace std;
struct node {
int v; //節點編號
int time; //邊權值
node(int _v, int _time) : v(_v), time(_time) {}; //建構函式
};
int Cmax; //每站最大容量
int N; //總站數
int Sp; //目的地
int M; //道路總數
int INF = 1000000000; //無窮大數
int C[502]; //每站現有公共自行車數量
vector<node> graph[502]; //無向圖
int d[502]; //儲存最短路徑長度
set<int> pre[502]; //記錄前一節點
vector<int> path;
vector<int> tempPath;
int optValue1 = INF;
int optValue2 = INF;
bool inq[502] = {false}; //標記節點是否在佇列中
int countInq[502] = {0}; //記錄節點的入隊次數
bool spfa(int s);
void dfs(int nowVisit);
int main() {
cin >> Cmax >> N >> Sp >> M;
for(int i = 1; i <= N; i++) {
cin >> C[i];
}
int Si, Sj, Tij;
for(int i = 0; i < M; i++) {
cin >> Si >> Sj >> Tij;
graph[Si].push_back(node(Sj, Tij));
graph[Sj].push_back(node(Si, Tij));
}
spfa(0);
dfs(Sp);
cout << optValue1 << " ";
for(int i = path.size() - 1; i >= 0; i--){
cout << path[i];
if(i != 0){
cout << "->";
}
}
cout << " " << optValue2 << endl;
return 0;
}
bool spfa(int s) {
for(int i = 0; i <= N; i++){
d[i] = INF;
}
d[s] = 0;
queue<int> q;
q.push(s);
inq[s] = true;
countInq[s]++;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = false;
for(int j = 0; j < graph[u].size(); j++){
int v = graph[u][j].v;
int time = graph[u][j].time;
if(d[u] + time < d[v]){
d[v] = d[u] + time;
pre[v].clear();
pre[v].insert(u);
if(!inq[v]){
q.push(v);
inq[v] = true;
countInq[v]++;
if(countInq[v] > N + 1){
return false;
}
}
}else if(d[u] + time == d[v]){
pre[v].insert(u);
if(!inq[v]){
q.push(v);
inq[v] = true;
countInq[v]++;
if(countInq[v] > N + 1){
return false;
}
}
}
}
}
return true;
}
void dfs(int nowVisit){
if(nowVisit == 0){
tempPath.push_back(nowVisit);
int provide = 0, recycle = 0;
for(int i = tempPath.size() - 2; i >= 0; i--){
if(C[tempPath[i]] - Cmax / 2 >= 0){
recycle += C[tempPath[i]] - Cmax / 2;
}else{
if(recycle >= -C[tempPath[i]] + Cmax / 2){
recycle -= -C[tempPath[i]] + Cmax / 2;
}else{
provide += -C[tempPath[i]] + Cmax / 2 - recycle;
recycle = 0;
}
}
}
if(provide < optValue1){
optValue1 = provide;
optValue2 = recycle;
path = tempPath;
}else if(provide = optValue1 && recycle < optValue2){
optValue2 = recycle;
path = tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(nowVisit);
set<int>::iterator it;
for(it = pre[nowVisit].begin(); it != pre[nowVisit].end(); it++){
dfs(*it);
}
tempPath.pop_back();
}
C++解題報告: