有A個村子和B個城堡,村子標號是1~A,城堡標號是A+1~B。馬里奧現在位於城堡B,他要帶公主回到村子1,他有一雙靴子,穿上之後可以不用時間就能從一個地方飛到另外一個地方,但是穿着靴子不能穿過城堡,穿靴子的次數也不能超過 K 次,一次不能超過 L km。求從 B 到 1 所用的最短時間。
分析:
最開始是在一本書上看到這道題目,說是Dijstra,於是打算來寫寫看。結果悲劇了。題目的關鍵點其實是在哪裏使用鞋子才能使得全局的時間最少,可以用 DP 來解決,用一種逆向思維,先來寫動態轉移方程,用 i 來表示目前到了 i 點,用 j 來表示到 i 點時穿了幾次靴子,dp[i][j] = min{dp[k][j-1],dp[k][j]+map[k][i]} 從方程中可以看出,需要知道任意兩個點之間的距離,也需要知道任意兩個點之間是否能夠穿靴子通過(比如考慮到 K,L),此時就需要用到求最短路徑了,由於此時用到的是任意兩個點之間的最短路徑,所以最好用 Floyd ,用 Dijstra 的話在求任意兩個點之間是否能夠穿靴子通過時就會出現點小麻煩。
好吧,其實我兩種都寫了。
貼下Dijstra的代碼
#include<stdio.h>
const int maxn = 150;
const int INF = 1000000000;
int mat[maxn][maxn],tmp[maxn][maxn],Dist[maxn];
int DP[maxn][20],newmat[maxn][maxn];
bool visited[maxn];
int n,A,B,L;
void Dijstra(int s)
{
int i,j,k,min;
for(i=1;i<=n;i++)
{
Dist[i] = mat[s][i];
visited[i] = false;
}
Dist[s] = 0;
visited[s] = true;
for(i=1;i<=n;i++)
{
k = -1;
min = INF;
for(j=1;j<=n;j++)
if(visited[j]==false&&min>Dist[j])
{
k = j;
min = Dist[j];
}
if(k!=-1)
{
visited[k] = true;
for(j=1;j<=n;j++)
{
if(Dist[k]+mat[k][j]<Dist[j])
{
Dist[j] = Dist[k] + mat[k][j];
}
}
}
}
}
void Dijstra1(int s)//爲了求tmp[][],tmp[][]保存的是任意兩點是否能穿鞋子通過。
{
int i,j,k,min;
for(i=1;i<=n;i++)
{
Dist[i] = mat[s][i];
visited[i] = false;
}
Dist[s] = 0;
visited[s] = true;
for(i=1;i<=n;i++)
{
k = -1;
min = INF;
for(j=1;j<=n;j++)
if(visited[j]==false&&min>Dist[j])
{
k = j;
min = Dist[j];
}
if(k!=-1)
{
visited[k] = true;
for(j=1;j<=n;j++)
{
if(Dist[k]+mat[k][j]<Dist[j]&&k<=A)
{
Dist[j] = Dist[k]+mat[k][j];
if(Dist[j]<=L)
tmp[s][j] = 0;
}
}
}
}
}
int main()
{
int i,j,t,m,K;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d%d",&A,&B,&m,&L,&K);
n = A+B;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
mat[i][j] = newmat[i][j] = tmp[j][i] = INF;//初始化
int a,b,c;
for(i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
mat[a][b] = mat[b][a] = c;
if(c<=L)
tmp[a][b] = tmp[b][a] = 0;
newmat[a][b] = newmat[b][a] = c;
}//構圖
for(i=1;i<=n;i++)
{
Dijstra1(i);
Dijstra(i);
for(j=1;j<=n;j++)
newmat[i][j] = Dist[j];
}
for(i=1;i<=n;i++)
DP[i][0] = newmat[1][i];//未使用加速靴時的時間
for(j=1;j<=K;j++)
DP[1][j] = 0;
for(i=2;i<=n;i++)
{
for(j=1;j<=K;j++)
{
int min = INF,tt;
for(int l = 1;l<i;l++)
{
if(!tmp[l][i])
tt = DP[l][j-1]>DP[l][j]+newmat[l][i]?DP[l][j]+newmat[l][i]:DP[l][j-1];
else
tt = newmat[l][i]+DP[l][j];
if(tt<min)
min = tt;
}
DP[i][j] = min;
}
}
printf("%d\n",DP[n][K]);
}
return 0;
}
再來比對一下Floyd的代碼#include<stdio.h>
const int maxn = 150;
const int INF = 1000000000;
int dp[maxn][20],mat[maxn][maxn],mind[maxn][maxn],tmp[maxn][maxn];
int n,A,B,L;
void Floyd()
{
int i,j,k;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
mind[i][j] = mat[i][j];
for(k=1;k<=n;k++)
{
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(mind[i][k]+mind[k][j]<mind[i][j])
{
mind[i][j] = mind[i][k] + mind[k][j];
if(k<=A&&mind[i][j]<=L)
tmp[i][j] = 1;
}
}
}
}
}
int main()
{
int t,K,m,i,j,a,b,c;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d%d",&A,&B,&m,&L,&K);
n = A+B;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(i==j)
mat[i][j] = 0;
else
mat[i][j] = INF;
tmp[i][j] = 0;
}
}//初始化
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
mat[a][b] = mat[b][a] = c;
if(c<=L)
tmp[a][b] = tmp[b][a] = 1;
}//初步構圖
Floyd();//mind[][]保存計算之後的最短路徑
for(i=1;i<=n;i++)
dp[i][0] = mind[1][i];
for(i=1;i<=K;i++)
dp[1][i] = 0;
for(i=2;i<=n;i++)
{
for(j=1;j<=K;j++)
{
int min = INF,tt;
for(int l=1;l<i;l++)
{
if(tmp[l][i])
tt = dp[l][j-1]<dp[l][j]+mind[l][i]?dp[l][j-1]:dp[l][j]+mind[l][i];
else
tt = dp[l][j] + mind[l][i];
if(tt<min)
min = tt;
}
dp[i][j] = min;
}
}
printf("%d\n",dp[n][K]);
}
return 0;
}