全解題報告索引目錄 -> 【北大ACM – POJ試題分類】
轉載請註明出處:http://exp-blog.com
-------------------------------------------------------------------------
大致題意:
有n座城市和m(1<=n,m<=10)條路。現在要從城市1到城市n。有些路是要收費的,從a城市到b城市,如果之前到過c城市,那麼只要付P的錢,如果沒有去過就付R的錢。求的是最少要花多少錢。
注意:路徑是有向的。
解題思路:
DFS。這題當有了思路後,做起來是沒有難度的,但是思維推算能力要求很高。
這題難點在於“城市與城市之間可能存在多條路徑”:
1、 輸入數據時可能會出現多條 從城市a到城市b的路徑信息,但是費用有所差別;
2、 對於 從城市a到城市b 的同一條路徑,允許重複走。
有人會問,重複走同一條路徑有什麼意義?單純增加費用而已,爲什麼不能標記所有路徑,每條路只允許走一次,這樣費用不是更少麼?
我開始也是陷入了這種思維,但是這種想法其實“對一半,錯一半”。
先來看一組數據:
6 5
1 2 1 10 10
2 3 4 10 100
2 4 2 15 15
4 1 1 12 12
3 6 6 10 10
如果每條路只允許走一次,那麼方案只有1個:
1à2à3à6 共135元
但這組數據的正確答案是67元。爲什麼?正確的方案如下:
1à2à4à1à2à3à6 共67元
顯然1à2重複走了一次,目的是爲了先到達城市4,從而使得2à3這段路的費用從100縮減到10元。
看到這裏很多同學好像就恍然大悟,但是問題馬上又來了。如果同一條路允許重複走,那麼就不能標記了,但一旦不標記,失去了搜索的限制條件,DFS就無法結束,不是陷入死循環了?
我剛纔說這種思路“對一半,錯一半”,“對”是對在“重複走會增加費用”,“錯”是錯在“重複走的對象不是某一條路,而是某一個環路”。在同一個環路重複走纔會真正增加費用。但是標記環路是很麻煩的,那麼能不能根據某一條路或某一個城市重複走過的次數來判斷當前所走的方案已經出現了環路? 答案是可以的。
上述的例子已經驗證過了,同一條路可以重複走,但是不能無限重複走,重複的次數是有限的。那麼應該重複多少次才合理?這與m值有關。題目的m值範圍爲<=10,那麼當人一個城市被到達的次數若 >3次(不包括3),所走的方案必然出現了環路(網上的同學稱之爲“閘數”)。
因此只需把bool vist[] 修改爲 int vist[] 進行標記,本題就能解決了。
===================================================================
PS:最近剛剛接觸了C++的面向對象,所以程序用class寫了一次,用C風格寫了一次。
//Memory Time
//248K 0MS
/*---- C++面向對象 ----*/
#include<iostream>
using namespace std;
class Road
{
public:
int a,b,c,p,r;
};
class info
{
public:
info():MinCost(2000) //C++的'='和'()'都能賦值
{ //但構造函數這裏只能用'()'
road=new Road[11];
vist=new int[11];
memset(vist,0,sizeof(int)*11);
vist[1]=1; //從城市1出發,因此預記錄到達1次
}
~info()
{
delete[] road;
delete[] vist;
}
void input(void);
void output(void);
void DFS(int a,int fee);
protected:
int n; //城市數
int m; //道路數
int MinCost; //最小總花費
int *vist; //記錄城市的訪問次數,每個城市最多經過3次
Road *road; //每條道路的付費規則
};
void info::input(void)
{
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>road[i].a>>road[i].b>>road[i].c>>road[i].p>>road[i].r;
return;
}
void info::output(void)
{
if(MinCost==2000)
cout<<"impossible"<<endl;
else
cout<<MinCost<<endl;
return;
}
void info::DFS(int a,int fee) //a:當前所在城市,fee:當前方案的費用
{
if(a==n && MinCost>fee)
{
MinCost=fee;
return;
}
for(int i=1;i<=m;i++) //枚舉道路
{
if(a==road[i].a && vist[ road[i].b ]<=3)
{
int b=road[i].b;
vist[b]++;
if(vist[ road[i].c ])
DFS(b,fee+road[i].p);
else
DFS(b,fee+road[i].r);
vist[b]--; //回溯
}
}
return;
}
int main(void)
{
info poj3411;
poj3411.input();
poj3411.DFS(1,0);
poj3411.output();
return 0;
}
===========華麗的分割線==============
//Memory Time
//248K 16MS
/*---- C風格 ----*/
#include<iostream>
using namespace std;
int n; //城市數
int m; //道路數
int vist[11]; //記錄城市的訪問次數,每個城市最多經過3次
int MinCost; //最小總花費
struct
{
int a,b,c,p,r;
}road[11]; //每條道路的付費規則
void DFS(int a,int fee) //a:當前所在城市,fee:當前方案的費用
{
if(a==n && MinCost>fee)
{
MinCost=fee;
return;
}
for(int i=1;i<=m;i++) //枚舉道路
{
if(a==road[i].a && vist[ road[i].b ]<=3)
{
int b=road[i].b;
vist[b]++;
if(vist[ road[i].c ])
DFS(b,fee+road[i].p);
else
DFS(b,fee+road[i].r);
vist[b]--; //回溯
}
}
return;
}
int main(void)
{
while(cin>>n>>m)
{
memset(vist,0,sizeof(vist));
vist[1]=1; //從城市1出發,因此預記錄到達1次
MinCost=2000;
for(int i=1;i<=m;i++)
cin>>road[i].a>>road[i].b>>road[i].c>>road[i].p>>road[i].r;
DFS(1,0);
if(MinCost==2000)
cout<<"impossible"<<endl;
else
cout<<MinCost<<endl;
}
return 0;
}
Sample Input
4 5
1 2 1 10 10
2 3 1 30 50
3 4 3 80 80
2 1 2 10 10
1 3 2 10 50
6 5
1 2 1 10 10
2 3 4 10 100
2 4 2 15 15
4 1 1 12 12
3 6 6 10 10
Sample Output
110
67
注意邊界數據:當輸入n==1時,輸出花費恆爲0