空間寶石「最短路+線段樹」
空間寶石「最短路+線段樹」
題目描述
zP1nG很清楚自己打不過滅霸,所以只能在自己出的題裡欺負他。
咳咳。這一次他得到了空間寶石Tesseract。
世界之樹連線著九界,此時滅霸和zP1nG都在九界的某個地方。而九界是相互無法到達的。zP1nG為了追殺滅霸,決定使用空間寶石的力量到達滅霸身邊。因為zP1nG不擅長使用空間寶石,無法直接開一條從自己的位置到滅霸的位置的傳送門,所以他只能無意識地使用空間寶石的力量。zP1nG想知道,在自己胡亂地使用空間寶石後,通過傳送門到達滅霸的位置最少需要多長時間。
具體地,九界的編號為 \(0\)~\(8\),共有 \(n\) 道傳送門,第 \(i\) 道傳送門為優先順序為 \(p_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*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;
}