時空寶石(Floyd + 線段樹)
題目描述
zP1nG很清楚自己打不過滅霸,所以只能在自己出的題裡欺負他。
咳咳。這一次他得到了空間寶石\(Tesseract\)。
世界之樹連線著九界,此時滅霸和zP1nG都在九界的某個地方。而九界是相互無法到達的。zP1nG為了追殺滅霸,決定使用空間寶石的力量到達滅霸身邊。因為zP1nG不擅長使用空間寶石,無法直接開一條從自己的位置到滅霸的位置的傳送門,所以他只能無意識地使用空間寶石的力量。zP1nG想知道,在自己胡亂地使用空間寶石後,通過傳送門到達滅霸的位置最少需要多長時間。
具體地,九界的編號為0~8,共有\(n\)道傳送門,第i道傳送門為優先順序為\(p_i\),由\(u_i\)到\(v_i\)
zP1nG會使用\(q\)次寶石的力量來嘗試進行傳送:其中第\(i\)次嘗試會開啟數道傳送門,這些傳送門的優先順序會形成\(s_i\)個區間。例如,在某次嘗試中zP1nG打開了三個區間\([1,2]\),\([4,7]\),\([9,10]\),那麼優先順序在這三個區間內的傳送門將全部被開啟並允許zP1nG穿過。你需要告訴zP1nG在此情況下從自己的位置\(z_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]));
}
}