1. 程式人生 > 其它 >題解 P7192 【[COCI2007-2008#6] GEORGE】

題解 P7192 【[COCI2007-2008#6] GEORGE】

題目傳送門

不扯閒話了

進入正題

偉大的 \(cpp\) 告訴我們,拿到題目一定要看一眼資料範圍。於是我們先來看一下點數和邊數。發現\(n\le10^3\)\(m\le10^4\),基本就是一個稠密圖,而且點數不多,於是我們可以選擇使用鄰接矩陣來存圖。

然後來考慮 \(Luka\) 應該怎麼走。顯然應該讓 \(T\) 先生先走,於是我們考慮記錄每條路的封鎖時間。

int tt=0;
F(i,1,g-1) {
	t[c[i]][c[i+1]]=t[c[i+1]][c[i]]=tt;
	//考慮其是雙向邊,應當將其反向再記錄一遍 
	//t[i][j]表示從路口i到路口j這一段路的管制開始時刻 
	tt+=f[c[i]][c[i+1]];//tt記錄當前時刻 
}

然後我們用 \(Dijkstra\) 跑一遍最短路即可。

細節見程式碼(稍微用了一點點巨集):

#include<bits/stdc++.h>
#define rd(n) scanf("%d",&n);
#define F(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;
const int N=1e3+10;
int n,m,a,b,k,g,f[N][N];
int c[N],t[N][N],dis[N];
bool vis[N];
struct P {
	int d,t;//d表示節點的編號,t表示到達該點的時間
	bool operator <(const P&a)const {
		return t>a.t;
	}
};
int drive(int u,int v) {
	if(dis[u]>=t[u][v]&&dis[u]<=t[u][v]+f[u][v]-1) {
		/*當到達時間恰好為管制時間
		最小時間為T先生到達時刻加上兩倍過路時間
		(T先生過路的時間加上Luka過路的時間,恰好為兩倍。) 
		*/
		return (f[u][v]<<1)+t[u][v];
	}
	return dis[u]+f[u][v];//否則就直接加過路需要的時間 
}
void Dij() {
	memset(dis,0x3f,sizeof(dis));//初始化為較大值
	dis[a]=k;
	priority_queue<P>q;//堆優化
	q.push({a,k});//從點a開始,時刻為k
	while(!q.empty()) {
		P t=q.top();
		q.pop();
		if(vis[t.d])continue;
		vis[t.d]=1;
		F(i,1,n) {
			if(f[t.d][i]) { //t.d與i之間存在一條邊
				int pos=drive(t.d,i);//計算到達i點的最短時間
				if(dis[i]>pos) {//鬆弛操作 
					dis[i]=pos;
					q.push({i,dis[i]});
				}
			}
		}
	}
}
int main() {
	rd(n)rd(m)rd(a)rd(b)rd(k)rd(g)
	F(i,1,g)rd(c[i])//c是過路所需時間 
	int u,v,w;
	F(i,1,m) {//鄰接矩陣存圖 
		rd(u)rd(v)rd(w)
		f[u][v]=w;//雙向邊 
		f[v][u]=w;
	}
	int tt=0;
	F(i,1,g-1) {
		t[c[i]][c[i+1]]=t[c[i+1]][c[i]]=tt;
		//考慮其是雙向邊,應當將其反向再記錄一遍
		//t[i][j]表示從路口i到路口j這一段路的管制開始時刻
		tt+=f[c[i]][c[i+1]];//tt記錄當前時刻
	}
	Dij();
	printf("%d",dis[b]-k);//開車時間應當減去k 
	return 0;
}

AC

完結撒花!