1. 程式人生 > 實用技巧 >空間寶石(巧用線段樹之二)

空間寶石(巧用線段樹之二)

題目

題面

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

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

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

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

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

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

穿過。你需要告訴\(zP1nG\)在此情況下從自己的位置\(z_i\)到達滅霸所在處\(t_i\)所需的最短時間。嘗試結束後所有傳送門會關閉。

輸入格式

\(1\)行包含兩個正整數\(n\)\(S\)\(S\)的含義見資料範圍與約定所述。(\(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 \cdot 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\leq n\leq 100,000\)\(1\leq p_i \leq 2,000\)\(u_i\neq v_i\)\(z_i\neq t_i\)\(1\leq w_i\leq 100,000,000\)\(1\leq \sum s_i\leq10,000\)

解說

本題涉及了大量對優先順序區間的查詢操作,很容易想到要用線段樹維護,但是顯然象普通線段樹一樣每個節點只存一個“數”是無法滿足這道題的要求的。那麼每個節點應該存什麼?整棵樹怎麼構建?又怎麼維護?

仔細看題不難發現,本題涉及的位置個數少的可憐,只有“九界”,用臨接表存圖——不,應該說開一堆臨接表存一堆不同的圖——都綽綽有餘。這不禁讓人眼前一亮:或許可以把一個臨接矩陣當成一個節點?

再繼續思考這種方法的可行性:線段樹的重要性質,兩個區間可並,它可以實現嗎?我們可以將所有優先順序相同的路都存在一張臨接矩陣上,並把葉子節點設定為不同優先順序的道路的初始矩陣。那麼我們進行矩陣乘法後得到的新矩陣即是經過兩種優先順序道路的最短路。更妙的是由於矩陣乘法不滿足交換律的性質,我們只能從左向右乘,正好滿足了不同優先順序道路之間的先後順序。

至此,這道題的做法已經很明瞭了:先分優先順序建立至多\(2000\)個葉子結點,並對每個葉子結點跑一遍\(Floyd\)預處理出只走該優先順序道路時的最短路,之後的建樹,查詢等操作基本和普通線段樹無異,只是把\(push \_ up\)操作變為兩個兒子進行矩陣乘法(注意順序)。

所以,這道題給了我一個很重要的啟示:應用線段樹要靈活。一般進行區間修改,查詢等操作的題目都可以使用線段樹解決。其結點型別也不一定是“數”,只要是具有可並性的資料型別都可以當成結點,只要再對其各種操作進行相應修改即可。

程式碼

#include<bits/stdc++.h>
#define int long long
#define mid (l+r>>1)
using namespace std;
const int lzw=2000+3;
struct node{
	int a[10][10];
	node(){
		memset(a,0x3f,sizeof(a));
		for(int i=0;i<=8;i++) a[i][i]=0;
	}
	void init(){
		for(int k=0;k<=8;k++)
			for(int i=0;i<=8;i++)
				for(int j=0;j<=8;j++)
					a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
	}
	node operator * (const node &r) const{
		node ans;
		for(int k=0;k<=8;k++)
			for(int i=0;i<=8;i++)
				for(int j=0;j<=8;j++)
					ans.a[i][j]=min(ans.a[i][j],a[i][k]+r.a[k][j]);
		return ans;
	}
}a[lzw],tree[lzw<<2];
struct inter{
	int l,r;
	bool operator < (const inter &A) const{
		return l<A.l;
	}
}all[100000+3];
int n,S,p,u,v,w,q,st,des,s,ql,qr;
void build(int rt,int l,int r){
	if(l==r){
		tree[rt]=a[l];
		return;
	}
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	tree[rt]=tree[rt<<1]*tree[rt<<1|1];
}
node query(int rt,int l,int r,int ll,int rr){
	if(l==ll&&r==rr) return tree[rt];
	if(rr<=mid) return query(rt<<1,l,mid,ll,rr);
	if(ll>mid) return query(rt<<1|1,mid+1,r,ll,rr);
	return query(rt<<1,l,mid,ll,mid)*query(rt<<1|1,mid+1,r,mid+1,rr);
}
signed main(){
	scanf("%lld%lld",&n,&S);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld%lld%lld",&p,&u,&v,&w);
		a[p].a[u][v]=min(a[p].a[u][v],w);
	}
	for(int i=1;i<=2000;i++) a[i].init();
	build(1,1,2000);
	scanf("%lld",&q);
	while(q--){
		scanf("%lld%lld%lld",&st,&des,&s);
		for(int i=1;i<=s;i++) scanf("%lld%lld",&all[i].l,&all[i].r);
		sort(all+1,all+1+s);
		node ans;
		for(int i=1;i<=s;i++) ans=ans*query(1,1,2000,all[i].l,all[i].r);
		if(ans.a[st][des]>=0x3f3f3f3f3f3f3f3f) printf("-1\n");
		else printf("%lld\n",ans.a[st][des]);
	}
	return 0;
}

幸甚至哉,歌以詠志。