空间宝石(巧用线段树之二)
题目
题面
\(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;
}
幸甚至哉,歌以咏志。