P1075 星门待群钥 官方题解
【部分分:测试点 1∼31\sim 31∼3】
- 原题为 P2446 [SDOI2010] 大陆争霸。
- 对每个区域 ,分别维护沿航路到达星门前的最早时刻 ,以及所有前置区域完成的最晚时刻 。
- 当全部前置区域都已经完成时,区域 真正能够进入的时刻为
- 每次线性扫描所有尚未进入、前置条件已经满足的区域,选择 最小者进入,再更新航路与依赖。时间复杂度为 。
【部分分:测试点 4∼74\sim 74∼7】
- 特殊性质保证 ,所有区域都没有前置条件。
- 此时 ,区域进入时刻只由航路抵达时刻决定,题目退化为非负边权有向图上的单源最短路。
- 从区域 出发运行普通 Dijkstra 即可,时间复杂度为 。
- 这一档说明完整问题仍以最短路为骨架,前置条件只改变一个区域何时能够被正式取出。
【部分分:测试点 8∼118\sim 118∼11】
- 航路与前置关系都从编号较小的区域指向编号较大的区域,因此计算区域 时,所有可能影响它的区域都已经计算完毕。
- 道路抵达部分满足
前置完成部分满足
- 于是按照编号从小到大计算
即可。时间复杂度为 。
【部分分:测试点 12∼1512\sim 1512∼15】
- 回到一般图。对每个区域 再维护未完成前置条件的数量 。
- 只有当 且 有限时,区域 才成为候选区域,其候选时刻为 。
- 每次线性扫描所有候选区域,取候选时刻最小者。进入后同时松弛出航路,并通知所有把它作为前置条件的区域。
- 这已经是完整算法,只是尚未使用堆。时间复杂度为 。
【正解】
-
对区域 维护三项信息:
-
:已经完成的区域沿航路抵达 的最早时刻。
-
:已经完成的前置区域中,完成时刻的最大值。
-
:尚未完成的前置区域数量。
-
初始时 ,其余 。所有 , 等于输入给出的前置数量。
-
当 且 时,把
放入小根堆。
- 从堆中取出候选时刻最小的区域 。若该记录已经过期,或者 已经进入,直接丢弃。
- 否则令
并确定区域 的最早进入时刻。
- 对每条航路 ,执行
若此时 ,就把新的候选时刻放入堆。
- 对每个把 作为前置区域的 ,执行
- 当 第一次变为 时,全部前置区域已经完成。若 有限,就把
放入堆。
-
航路更新可能多次改善 ,所以堆中允许保留旧记录。取出时比较当前值即可忽略过期记录。
-
证明仍然沿用 Dijkstra 的贪心顺序。设当前堆中最小候选时刻为 ,对应区域 。
-
任何尚未进入的区域,其真正进入时刻都不小于当前候选时刻。若一条尚未处理的路径将来抵达 ,它必须先经过某个尚未进入的区域,再经过非负边权航路,因此抵达时刻不可能小于 。
-
的全部前置区域已经完成, 也不会再改变。故 已经是最终答案,可以安全确定。
-
归纳处理所有区域,算法正确。
-
每条航路只在起点进入时松弛一次,每条前置关系只在前置区域进入时处理一次。
-
每次有效更新至多向堆中加入一条记录,堆规模为 。
-
时间复杂度为
空间复杂度为 。
- 关键始终是区分“信号已经抵达星门”和“星门已经允许进入”,两项时间分别取最小值与最大值,最后再取较大者。
【参考代码】
/*Author:EhundateghDate:2026/8/26Name:gateway.cppYou steal,I kill.*/#include <queue>#include <vector>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 200010#define MAXM 500010using namespace std; const long long INF=0x3f3f3f3f3f3f3f3fll;int T,n,m,Head[MAXN],NeedHead[MAXN],Deg[MAXN],Total,NeedTotal;long long Road[MAXN],Pre[MAXN],Ans[MAXN];bool Visited[MAXN]; struct edge{ int Ed,Next,Value;}Edge[MAXM],Need[MAXM]; void Edge_Add(int St,int Ed,int Value){Edge[++Total]={Ed,Head[St],Value};Head[St]=Total;}void Need_Add(int St,int Ed){Need[++NeedTotal]={Ed,NeedHead[St],0};NeedHead[St]=NeedTotal;} priority_queue<pair<long long,int>,vector<pair<long long,int> >,greater<pair<long long,int> > > Q; void Push(int Now){ if(!Deg[Now]&&Road[Now]!=INF) Q.emplace(max(Road[Now],Pre[Now]),Now);} void Solve(){ int In1,In2,In3,k; scanf("%d%d",&n,&m); Total=NeedTotal=0; memset(Head,0,sizeof(Head)); memset(NeedHead,0,sizeof(NeedHead)); memset(Deg,0,sizeof(Deg)); memset(Pre,0,sizeof(Pre)); memset(Ans,0,sizeof(Ans)); memset(Visited,0,sizeof(Visited)); memset(Road,0x3f,sizeof(Road)); while(!Q.empty()) Q.pop(); for(int i=1;i<=m;i++){ scanf("%d%d%d",&In1,&In2,&In3); Edge_Add(In1,In2,In3); } for(int i=1;i<=n;i++){ scanf("%d",&k);Deg[i]=k; while(k-->0){scanf("%d",&In1);Need_Add(In1,i);} } Road[1]=0;Push(1); while(!Q.empty()){ long long Value=Q.top().first; int Now=Q.top().second;Q.pop(); if(Visited[Now]||Value!=max(Road[Now],Pre[Now])) continue; Visited[Now]=true;Ans[Now]=Value; for(int i=Head[Now];i;i=Edge[i].Next){ int To=Edge[i].Ed; if(Road[To]>Value+Edge[i].Value){ Road[To]=Value+Edge[i].Value; Push(To); } } for(int i=NeedHead[Now];i;i=Need[i].Next){ int To=Need[i].Ed; Pre[To]=max(Pre[To],Value); Deg[To]--; Push(To); } } for(int i=1;i<=n;i++) printf("%lld%c",Ans[i],i==n?'\n':' ');} int main(){ int c; scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}