1. 程式人生 > 實用技巧 >【GDFZOJ 1463】郵遞員送信

【GDFZOJ 1463】郵遞員送信

題目

  • 每次只能帶一樣東西
  • 求送完這 \(N−1\) 樣東西並且最終回到郵局最少需要多少時間。

這幾個關鍵句暗示我們:這道題需要求最短路

我們將總路徑拆成兩類。

  1. 第一類是由郵局(節點\(1\))發散出來的最短路徑和。這一類顯然是單源最短路徑和。
  2. 第二類是由其他店到郵局(節點\(1\))的路徑和。這一類處理方法比較困難,下面羅列出我在比賽時的兩種處理方法。

部分分

每個點都跑一次Dijkstra,再求出兩點之間的最短路徑和。此方法有大量冗餘計算,效率低下,複雜度為\(O(n^2\ \log_2n)\)

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=1e5+5;
struct edge {
	int to,w,nxt;
} e[M];
int n,tmp,head[N],vis[N],dis[N];
void add(int x,int y,int z) {
	e[++tmp].to=y,e[tmp].w=z,e[tmp].nxt=head[x],head[x]=tmp;
}
priority_queue<pair<int,int>> q;
inline void dijkstra(int u) {
	priority_queue<pair<int,int>> q;
	memset(dis,0x7f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	q.push(make_pair(0,u)),dis[u]=0;
	while(q.size()) {
		u=q.top().second,q.pop();
		if(vis[u]) {
			continue;
		}
		vis[u]=1;
		for(int i=head[u],v,w; i; i=e[i].nxt) {
			v=e[i].to,w=e[i].w;
			if(dis[u]+w<dis[v]) {
				dis[v]=dis[u]+w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}
int main() {
	int m;
	scanf("%d %d",&n,&m);
	for (int i=0,u,v,w; i<m; i++) {
		scanf("%d %d %d",&u,&v,&w);
		add(u,v,w);
	}
	memset(dis,0x7f,sizeof(dis));
	dijkstra(1);
	int ans=0;
	for(int i=2; i<=n; i++) {
		ans+=dis[i];
	}
	for(int i=2; i<=n; i++) {
		dijkstra(i),ans+=dis[1];
	}
	printf("%d",ans);
	return 0;
}

正解

我們需要一種新的方法。

在求第二類路徑和時,我們發現:如果我們對這個圖建反圖,那麼第二類路徑和類似於第一類路徑和。我們可以用同樣的時間複雜度(\(O(n\ \log_2n)\))求解,不會超時。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=1e5+5;
int n;
namespace P1 {
	struct edge {
		int to,w,nxt;
	} e[M];
	int tmp,head[N],vis[N],dis[N];
	void add(int x,int y,int z) {
		e[++tmp].to=y,e[tmp].w=z,e[tmp].nxt=head[x],head[x]=tmp;
	}
	priority_queue<pair<int,int>> q;
	inline void dijkstra(int u) {
		priority_queue<pair<int,int>> q;
		memset(dis,0x7f,sizeof(dis));
		memset(vis,0,sizeof(vis));
		q.push(make_pair(0,u)),dis[u]=0;
		while(q.size()) {
			u=q.top().second,q.pop();
			if(vis[u]) {
				continue;
			}
			vis[u]=1;
			for(int i=head[u],v,w; i; i=e[i].nxt) {
				v=e[i].to,w=e[i].w;
				if(dis[u]+w<dis[v]) {
					dis[v]=dis[u]+w;
					q.push(make_pair(-dis[v],v));
				}
			}
		}
	}
}
namespace P2 {
	struct edge {
		int to,w,nxt;
	} e[M];
	int tmp,head[N],vis[N],dis[N];
	void add(int x,int y,int z) {
		e[++tmp].to=y,e[tmp].w=z,e[tmp].nxt=head[x],head[x]=tmp;
	}
	priority_queue<pair<int,int>> q;
	inline void dijkstra(int u) {
		priority_queue<pair<int,int>> q;
		memset(dis,0x7f,sizeof(dis));
		memset(vis,0,sizeof(vis));
		q.push(make_pair(0,u)),dis[u]=0;
		while(q.size()) {
			u=q.top().second,q.pop();
			if(vis[u]) {
				continue;
			}
			vis[u]=1;
			for(int i=head[u],v,w; i; i=e[i].nxt) {
				v=e[i].to,w=e[i].w;
				if(dis[u]+w<dis[v]) {
					dis[v]=dis[u]+w;
					q.push(make_pair(-dis[v],v));
				}
			}
		}
	}
}
int main() {
	int m;
	scanf("%d %d",&n,&m);
	for (int i=0,u,v,w; i<m; i++) {
		scanf("%d %d %d",&u,&v,&w);
		P1::add(u,v,w),P2::add(v,u,w);
	}
	memset(P1::dis,0x7f,sizeof(P1::dis));
	memset(P2::dis,0x7f,sizeof(P2::dis));
	P1::dijkstra(1),P2::dijkstra(1);
	int ans=0;
	for(int i=2; i<=n; i++) {
		ans+=P1::dis[i]+P2::dis[i];
	}
	printf("%d",ans);
	return 0;
}

後記

在這道題裡,要注意計算冗餘量是否夠多(類似時間複雜度)。這道題一開始的時間複雜度\(O(n^2\ \log_2n)\)與後來的\(O(n\ \log_2n)\)區別還是很大的,例如我的提交記錄:

所以說:

  1. 注意時間複雜度

  2. 在第1條滿足後儘量減少複雜度至在時間方面大概率(甚至\(100\%\))通過。