1087 All Roads Lead to Rome (30分) Dijkstra維護最大價值最少節點的最短路,最短路的條數 Apare_xzc

1087 All Roads Lead to Rome (30分) Dijkstra Apare_xzc


題目鏈接:1087 All Roads Lead to Rome (30分)


題面:

在這裏插入圖片描述

題意:

        你是一個導遊,要讓你的顧客從當前城市出發,到達“ROM”這個目的地,圖中可以遊覽別的城市。每個城市都有一個可以使旅客快樂的值。從一個城市到另一個城市要花費一定的路費。你要求出從起點到終點的旅客花費最少的路徑。如果有多條路徑,那麼選擇沿途快樂值之和最大的路線。如果仍有多條路線,那麼選擇圖中經過城市個數最少的路線(我的翻譯)。
        數據保證有且僅由一條路線。一共有n個城市,城市之間有k條雙向道路。每個城市都有一個名字,用三個字母的字符串表示。


輸入格式:

        第1行,n,k,st 表示城市個數,道路個數,以及起點城市的名字。
        第2行到第n行, 這n-1行是除了起點之外的其它城市的信息。每行一個字符串和一個整數,代表城市的名字和快樂值。
        下面的m行,每行都有兩個字符串u,v和一個整數d,代表城市u,v之間有一條花費爲d的道路。


輸出格式:

        輸出有2行,第一行是用空格隔開的4個整數,分別表示最少花費道路的數目,最少的花費值,最大的快樂值之和,最大的平均快樂值(保留整數) 第二行用st->p1->p2…->ROM的格式輸出所求的路徑,具體格式參見樣例。


樣例輸入:

6 7 HZH
ROM 100
PKN 40
GDN 55
PRS 95
BLN 80
ROM GDN 1
BLN ROM 1
HZH PKN 1
PRS ROM 2
BLN HZH 2
PKN GDN 1
HZH PRS 1

樣例輸出:

3 3 195 97
HZH->PRS->ROM

AC


分析:

        根據題意,要求最短路,由於是沒有負權邊的無向圖,我們肯定用Dijkstra算法。這道題,不但要求最短路的大小(最小花費),還要求阻斷路的條數。以及輸出一條最短的,快樂值之和最大的,節點最少的道路。這其實都是更新節點時候幾個if-else的事情,並不難。
        首先我們說結點的表示。由於輸入的是字符串代表城市的名字,我們要把每個城市名字變成編號方便我們計算。可以建立string和int之間的映射,用map維護最爲方便。
        再說說存圖。由於這個題結點個數n<=200,所以用鄰接表或者鄰接矩陣皆可以。我更傾向於使用數組模擬的鄰接表(鏈式前向星)
        還有Dijkstra的寫法,可以不用堆優化,因爲數據不大,O(n^2)的複雜度可以接受。但使用優先隊列優化可以使時間複雜度爲Klog2K,對於稀疏圖更加快。
        關於維護最短路,我們知道Dijkstra其實是一種貪心算法,可以求出起點到其它節點的最短路徑(單源最短路)。從起點開始,起點到自己的距離爲0。我們循環n-1次,每次找出還沒有求出最短路的剩餘的節點中距離起點最近的一個節點u,於是這個距離一定是u到起點的最短距離。再用u去更新所有與u相鄰的節點v到起點的最短距離。Dijkstra算法求最短路的原理同Kruskal算法求最小生成樹。之所以要設置標記數組vis, 是因爲之前已經求得最短路的結點已經得到最優值,即使再更新,對於答案也沒有改變。是多餘的計算。(扯遠了)
        關於求最短路徑的條數,我們並不需要dfs搜索。只需要在“用當前選出距離起點最近的節點u來更新和u相鄰的節點v”的時候,順便維護一個cntRoute[]數組,表示從起點到該節點,滿足路徑長度最短(花費最小)的路徑的條數。如果點u和v之間有一條邊,cnt[u]已知,那麼就能用cnt[u]來更新cnt[v]

  • 在用u更新v的時候,如果dis[u] + cost[u][v] < dis[v], 那麼當前原點到v的最短路一定只能先去u,再去v ,那麼 cnt[v] = cnt[u]
  • 如果dis[u] + cost[u][v] == dis[v] 那麼,原點可以先去別的點,再去v, 亦可以先去u,再到v, 在當前這個最短路徑 dis[v] 之下, 路徑的條數又多了 幾條原點先去u在去v的路徑,此時 cnt[v] += cnt[u]

        關於維護最大的快樂值,只需要在用u更新v的時候,如果花費相同,就選擇快樂值大的路徑,如果快樂值一樣大,那麼就選擇路徑上結點個數少的路徑(因爲要求城市平均快樂值最大)
        關於記錄路徑,我們不需要每個結點都記錄全部的路徑,只需要記錄每個結點的前驅節點即可。這樣的話只需要循環一遍,每次找前驅,直到從終點找到起點,記錄到棧或數組裏面,逆序輸出即可,或者路徑不長的話可以遞歸。


我的AC代碼:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 200+10;
const int maxm = 40000+10;
map<string,int> mp; //string->int 城市名到編號的映射 
LL val[maxn]; //每個城市的快樂值 
int n,m,cnt=1; //n是城市個數,m是道路數目,cnt用於給城市分配編號 
string st,Name[maxn];
//st是起點的城市名,Name[]數組記錄城市編號對應的名字 
struct Node{ 
	int to,Next;
	LL d;
}node[maxm];//維護鏈式前向星(數組模擬鄰接表) 
int head[maxn],tot; //頭結點,節點總數 
void initEdge()
{ //初始化鄰接表 
	tot = 0;
	memset(head,-1,sizeof(head));	
} 
void addedge(int u,int v,LL d)
{ //加邊 
	node[tot].to = v;
	node[tot].d = d;
	node[tot].Next = head[u];
	head[u] = tot++;	
} 
struct P{ //Dijkstra()中優先隊列存儲的數據結構 
	int id; //結點編號 
	LL dis; //結點當前距離起點的最短距離 
	bool operator < (const P & rhs)const{
		return dis > rhs.dis;
	} //重載小於號 
	P(){} //無參構造函數 
	P(int _id,LL _dis)
	{ //構造函數 
		id = _id;
		dis = _dis; 
	} 
//	friend ostream& operator << (ostream& out,const P& p)
//	{
//		out<<"id = "<<p.id<<", dis = "<<p.dis;
//		return out;
//	}
}p;
int Pre[maxn]; //記錄前驅節點 
bool vis[maxn]; //標記數組 
LL sum[maxn]; //記錄最大快樂值之和 
LL dis[maxn]; //記錄最短距離 
int Num[maxn]; //記錄最短路徑上的結點個數 
LL cntRoute[maxn]; //記錄最短路的條數 
void Dijkstra()
{
	priority_queue<P> Q;
	memset(vis,false,sizeof(vis));
	memset(dis,0x3f,sizeof(dis)); //初始化 
	dis[1] = 0; //起點的編號爲1 
	sum[1] = val[1];
	cntRoute[1] = 1;
	Q.push(P(1,0)); //起點入隊 
	while(!Q.empty())
	{
		p = Q.top();Q.pop();
		if(vis[p.id]) continue; 
		vis[p.id] = true; //標記 
		for(int i=head[p.id];i!=-1;i=node[i].Next)
		{  //用u更新v 
			int to = node[i].to;
			if(vis[to]) continue;
			LL d = node[i].d;
			if(dis[p.id]+d<dis[to]) //距離更短,直接更新 
			{
				dis[to] = dis[p.id]+d; 
				Pre[to] = p.id;
				sum[to] = sum[p.id]+val[to];
				Num[to] = Num[p.id]+1;
				cntRoute[to] = cntRoute[p.id];
				Q.push(P(to,dis[to]));
			}	
			else if(dis[p.id]+d==dis[to]) //距離相同 
			{
				cntRoute[to] += cntRoute[p.id];
				if(sum[p.id]>sum[Pre[to]])
				{
					Pre[to] = p.id;
					sum[to] = sum[p.id]+val[to];
					Num[to] = Num[p.id]+1;
					Q.push(P(to,dis[to]));
				}
				else if(sum[p.id]==sum[Pre[to]]&&Num[p.id]+1<Num[to])
				{
					Pre[to] = p.id;
					Num[to] = Num[p.id]+1;
					Q.push(P(to,dis[to]));
				}
			}
		}		
	}
	int ed = mp[string("ROM")];
	printf("%lld %lld %lld %lld\n",cntRoute[ed],dis[ed],sum[ed],sum[ed]/Num[ed]);
	stack<int> Sta;
	while(ed!=1) //得出路徑 
	{
		Sta.push(ed);
		ed = Pre[ed];
	}
	cout<<Name[1];
	while(!Sta.empty())
	{
		cout<<"->"<<Name[Sta.top()];
		Sta.pop();	
	}	
	puts("");
}
int main()
{
//	freopen("in.txt","r",stdin);
	cin>>n>>m>>st;
	string u,v;
	LL d;
	mp[st] = 1; //起點爲1 
	Name[1] = st;
	for(int i=1;i<n;++i)
	{
		cin>>u>>d;
		if(mp.count(u)) val[mp[u]] = d;
		else 
		{ //給城市分配編號 
			mp[u] = ++cnt; val[cnt] = d;
			Name[cnt] = u;
		}
	}
	int iu,iv;
	initEdge();
	for(int i=1;i<=m;++i)
	{
		cin>>u>>v>>d;
		iu = mp[u], iv = mp[v];
		addedge(iu,iv,d);
		addedge(iv,iu,d);
	} 
	memset(Pre,0,sizeof(Pre));
	Dijkstra();
	
	return 0;
}

AC愉快~
xzc
2020.2.5 22:26


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章