1. 程式人生 > 實用技巧 >時空寶石(Floyd + 線段樹)

時空寶石(Floyd + 線段樹)

題目描述

zP1nG很清楚自己打不過滅霸,所以只能在自己出的題裡欺負他。

咳咳。這一次他得到了空間寶石\(Tesseract\)

世界之樹連線著九界,此時滅霸和zP1nG都在九界的某個地方。而九界是相互無法到達的。zP1nG為了追殺滅霸,決定使用空間寶石的力量到達滅霸身邊。因為zP1nG不擅長使用空間寶石,無法直接開一條從自己的位置到滅霸的位置的傳送門,所以他只能無意識地使用空間寶石的力量。zP1nG想知道,在自己胡亂地使用空間寶石後,通過傳送門到達滅霸的位置最少需要多長時間。

具體地,九界的編號為0~8,共有\(n\)道傳送門,第i道傳送門為優先順序為\(p_i\),由\(u_i\)\(v_i\)

,需要花費\(w_i\)個時間的單向門。傳送的規則為:zP1nG按順序穿過的傳送門的優先順序必須是單調不降的。例如,zP1nG穿過了三道傳送門到達滅霸的位置,這三道傳送門的優先順序分別為1→2→2即為一種合法的方式,而優先順序分別為1→2→1是不合法的。

zP1nG會使用\(q\)次寶石的力量來嘗試進行傳送:其中第\(i\)次嘗試會開啟數道傳送門,這些傳送門的優先順序會形成\(s_i\)個區間。例如,在某次嘗試中zP1nG打開了三個區間\([1,2]\)\([4,7]\)\([9,10]\),那麼優先順序在這三個區間內的傳送門將全部被開啟並允許zP1nG穿過。你需要告訴zP1nG在此情況下從自己的位置\(z_i\)

到達滅霸所在處\(t_i\)所需的最短時間。嘗試結束後所有傳送門會關閉。

輸入格式

\(1\)行包含兩個正整數\(n\)\(S\)\(S\)的含義見資料範圍與約定所述。

\(2\)\(n+1\)行每行包含4個正整數\(p_i\)\(u_i\)\(v_i\)\(w_i\)

\(n+2\)行包含一個正整數\(q\)

\(n+3\)至第\(n+q+2\)行每行若干個正整數,其中前三個數為\(z_i\)\(t_i\)\(s_i\),之後為\(2 \times si\)個正整數,表示每個區間的左右端點。

各變數具體含義見題目描述所述。

輸出格式

對於zP1nG進行的每次嘗試,輸出一行一個數表示從zP1nG的位置到滅霸的位置所需的最短時間,如果zP1nG無法到達滅霸的位置則輸出-1

樣例

樣例輸入

  6 2
  1 2 4 1
  2 1 3 3
  3 1 2 2
  4 3 4 5
  5 2 4 3
  6 1 4 2
  4
  1 4 1 1 3
  1 4 1 1 4
  1 4 2 5 5 2 3
  1 4 1 1 6

樣例輸出

  -1
  8
  5
  2

資料範圍與約定

5為無特殊限制

題解

很神奇的一道題,考試的時候一頭霧水,看著像最短路但總是缺點什麼。要知道道路的優先順序單憑一次的Dij或Spfa都是無法解決的,因為無論怎麼加限制,我們都無法保證在鬆弛時所做的那一個選擇一定是最優的,這點大家可以通過手動模擬資料試驗一下。

所以這道題不能用大眾的Dij或Spfa來解決。

面對這種限制條件極其噁心,可能情況極其多的型別,最好的辦法,那就是把所有情況都算一遍!

那麼我們剩下的,也就是隻有萬年小冷門(其實並沒有)的Floyd了!

其實題幹中已經說明節點數一共也就有9個,也就是說我們完全可以用聯接矩陣來儲存整張圖,\(n^3\)的效率完全可以接受,這就給了Floyd操作的空間。而對於優先順序的問題,通過研究資料我們知道優先順序最多有2000個,在\(9^3\)的基礎上再乘上2000,這樣的時空效率我們也是可以接受的。所以我們的辦法就是針對每一個優先順序都建一個聯接矩陣,然後以等級作為區間構建線段樹。

未完待續...

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2000 + 10;
char buf[1 << 20], *p1 = buf, *p2 = buf;
//fread版快讀,輸入結束後手動輸入EOF結束輸入 
char getc() {
	if(p1 == p2) {
		p1 = buf;
		p2 = buf + fread(buf, 1, 1 << 20, stdin);
		if(p1 == p2) return EOF;
	}
	return *p1++;
}
inline int read() {
	int s = 0, w = 1; char c = getc();
	while(c < '0' || c > '9') {if(c == '-') w = -1; c = getc();}
	while(c >= '0' && c <= '9') s = s * 10 + c - '0', c = getc();
	return s * w;
}
struct node {
	int l, r;
	bool operator <(const node& x)const {
		return l < x.l;
	}
}q[maxn];//儲存每一個可以開啟的區間 
struct Maxtri {
	int dis[10][10];
	void Init() {
		//有點類似於單位矩陣的初始化 
		for(int i = 0; i < 9; i++)
			for(int j = 0; j < 9; j++)
				dis[i][j] = (i == j ? 0 : 0x3f3f3f3f); 
	}
}G[maxn];//為每一個優先順序都建一張圖 
struct tree {
	Maxtri data;
	int l, r;
}t[maxn << 2];
inline Maxtri Update(Maxtri a, Maxtri b) {
	Maxtri c;
	c.Init();
	//標準的floyd最短路 ,又有點像矩陣乘法 
	for(int k = 0; k < 9; k++)
		for(int i = 0; i < 9; i++)
			for(int j = 0; j < 9; j++) 
				c.dis[i][j] = min(c.dis[i][j], a.dis[i][k] + b.dis[k][j]);
	return c;
}
#define tl t[u].l
#define tr t[u].r
#define ls (u << 1)
#define rs (u << 1 | 1)
void Pushup(int u) {
	t[u].data = Update(t[ls].data, t[rs].data);
}
void Build(int u, int l, int r) {
	//每個線段樹節點的所代表的區間都是優先順序的區間 
	t[u].l = l;
	t[u].r = r;
	if(l == r) {
		t[u].data = G[l];
		return;
	}
	int mid = (tl + tr) >> 1;
	Build(ls, l, mid);
	Build(rs, mid + 1, r);
	Pushup(u);
}
Maxtri Ask(int u, int l, int r) {
	if(l <= tl && tr <= r) {
		return t[u].data;
	}
	int mid = (tl + tr) >> 1;
	bool flag = 0;
	Maxtri ans;
	if(l <= mid) {
		ans = Ask(ls, l, r);
		flag = 1;
	}
	if(r > mid) {
		if(flag) ans = Update(ans, Ask(rs, l, r));
		else ans = Ask(rs, l, r);
	}
	return ans;
}
int main() {
	int n = read(), S = read();
	for(int i = 1; i <= 2001; i++) G[i].Init();
	int p, u, v, w;
	for(int i = 1; i <= n; i++) {
		p = read(), u = read(), v = read(), w = read();
		G[p].dis[u][v] = min(G[p].dis[u][v], w);
	}
	for(register int t = 1; t <= 2001; t++) //事先預處理單獨每一級優先順序的最短路 
		for(register int k = 0; k < 9; k++)
			for(register int i = 0; i < 9; i++)
				for(register int j = 0; j < 9; j++)
					G[t].dis[i][j] = min(G[t].dis[i][j], G[t].dis[i][k] + G[t].dis[k][j]);
	//線段樹主要是解決涉及多個優先順序的最短路 
	//按照優先順序作為區間構造線段樹,每一個區間內的樹節點所記錄的資料,就是每一個優先順序區間內所能達到的最短路 
	Build(1, 1, 2001);
	int cas = read();
	int st, ed, siz;
	while(cas--) {
		st = read(), ed = read(), siz = read();
		for(register int i = 1; i <= siz; i++) //讀入每個可使用的優先順序區間 
			q[i].l = read(), q[i].r = read();
		sort(q + 1, q + 1 + siz);  
		Maxtri ans = Ask(1, q[1].l, q[1].r); //我們按照優先順序作為區間構造了線段樹,更新答案時直接在相應的區間內查詢即可 
		for(register int i = 2; i <= siz; i++) {
			ans = Update(ans, Ask(1, q[i].l, q[i].r));
		}
		printf("%d\n", (ans.dis[st][ed] == 0x3f3f3f3f ? -1 : ans.dis[st][ed]));
	}
}