1. 程式人生 > 實用技巧 >空間寶石「最短路+線段樹」

空間寶石「最短路+線段樹」

空間寶石「最短路+線段樹」

題目描述

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*s_i\) 個正整數,表示每個區間的左右端點。

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

輸出格式

對於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

資料範圍與提示

對於100%的資料,\(1≤n≤100,000,1≤S≤5,1≤pi≤2,000,ui≠vi,zi≠ti,1≤wi≤100,000,000,1≤∑si≤10,000\)

思路分析

這題夠毒瘤的

  • 首先第一眼看上去應該是讓我們求一個帶有奇奇怪怪限制的最短路,再進一步看,發現這題只有9個節點???暴力蠢蠢欲動然後上去糊了一個二維spfa完美死掉,我太菜了
  • 只有九個節點,這時侯肯定是鄰接矩陣存邊最方便了,而相應的 \(Floyd\) 也就呼之欲出。不同優先順序的邊可以存在不同的矩陣裡
  • 那麼關鍵問題來了,不同優先順序要怎麼合併在一起?其實只要將兩個矩陣一起再跑一遍 \(Floyd\) 就可以,聯想到矩陣乘,稍微改成 \(Floyd\) 的形式就行了(因為這兩個形式差不多)
  • 到這裡還並不是最優解,由於我們每次開啟的傳送門是一個區間,這當然是線段樹最擅長的,通過線段樹合併即可

詳見程式碼

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
#define N 2020
#define R register
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int inf = 0x3f3f3f3f;
int n,S,q,Max = -1;
struct Martix{ //鄰接矩陣來存不同優先順序
	int a[10][10];
	Martix(){ //初始化
		memset(a,0x3f,sizeof(a));
		for(int i = 0;i < 9;i++)a[i][i] = 0;
	}
	Martix operator*(const Martix &r)const{ //魔改一下矩陣乘
		Martix res;
		for(int k = 0;k < 9;k++){
			for(int i = 0;i < 9;i++){
				for(int j = 0;j < 9;j++)
					res.a[i][j] = min(res.a[i][j],a[i][k]+r.a[k][j]);
			}
		}
		return res;
	}
}a[N],tr[N<<2];
#define ls u<<1
#define rs u<<1|1
void build(int u,int l,int r){ //線段樹進行合併
	if(l==r){tr[u] = a[l];return;}
	int mid = (l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	tr[u] = tr[ls]*tr[rs]; //注意是左右兒子放在一起跑一遍Floyd
}
Martix query(int u,int l,int r,int s,int t){
	if(s<=l&&t>=r)return tr[u];
	int mid = (l+r)>>1;
	if(t<=mid)return query(ls,l,mid,s,t);
	if(s>mid)return query(rs,mid+1,r,s,t);
	return query(ls,l,mid,s,t)*query(rs,mid+1,r,s,t);
}
struct node{ //結構體放在優先佇列裡用,根據優先順序,即左端點小的優先
	int l, r;
	node(){}
	node(int _l,int _r){
		l = _l,r = _r;
	}
	bool operator <(const node &a)const{
		return l > a.l;
	}
};
int main(){
	n = read(),S = read();
	for(int i = 1;i <= n;i++){
		int p,u,v,w;
		p = read(),u = read(),v = read(),w = read();
		if(a[p].a[u][v]>=w)a[p].a[u][v] = w;//處理重邊
		Max = max(Max,p); //處理出線段樹的右端點
	}
	for(int th  = 1;th <= Max;th++){ //兩個矩陣放一起之前先求出同一矩陣的最短路
		for(int k = 0;k < 9;k++){
			for(int i = 0;i < 9;i++){
				for(int j = 0;j < 9;j++){
					a[th].a[i][j] = min(a[th].a[i][j],a[th].a[i][k]+a[th].a[k][j]);
				}
			}
		}
	}
	build(1,1,Max);
	q = read();
	for(int i = 1;i <= q;i++){
		int z,t,s;z = read(),t = read(),s = read();
		priority_queue<node>q;
		for(int j = 1;j <= s;j++){
			int l,r;l = read(),r = read();
			q.push(node(l,r));
		}
		Martix ans;
		for(int j = 1;j <= s;j++){
			node p = q.top();q.pop();
			int l = p.l,r = p.r;
			ans = ans*query(1,1,Max,l,r); //每次詢問乘上就好
		}
		if(ans.a[z][t]<inf)printf("%d\n",ans.a[z][t]);
		else puts("-1");
	}
	return 0;
}