← 返回题解列表

P1075 星门待群钥 官方题解

【部分分:测试点 1∼31\sim 31∼3】

  • 原题为 P2446 [SDOI2010] 大陆争霸
  • 对每个区域 ii,分别维护沿航路到达星门前的最早时刻 rir_i,以及所有前置区域完成的最晚时刻 pip_i
  • 当全部前置区域都已经完成时,区域 ii 真正能够进入的时刻为
ti=max(ri,pi).t_i=\max(r_i,p_i).
  • 每次线性扫描所有尚未进入、前置条件已经满足的区域,选择 tit_i 最小者进入,再更新航路与依赖。时间复杂度为 O(n2+m+s)\mathcal O(n^2+m+s)

【部分分:测试点 4∼74\sim 74∼7】

  • 特殊性质保证 s=0s=0,所有区域都没有前置条件。
  • 此时 pi=0p_i=0,区域进入时刻只由航路抵达时刻决定,题目退化为非负边权有向图上的单源最短路。
  • 从区域 11 出发运行普通 Dijkstra 即可,时间复杂度为 O((n+m)logn)\mathcal O((n+m)\log n)
  • 这一档说明完整问题仍以最短路为骨架,前置条件只改变一个区域何时能够被正式取出。

【部分分:测试点 8∼118\sim 118∼11】

  • 航路与前置关系都从编号较小的区域指向编号较大的区域,因此计算区域 ii 时,所有可能影响它的区域都已经计算完毕。
  • 道路抵达部分满足
ri=minui(tu+wu,i),r_i=\min_{u\to i}(t_u+w_{u,i}),

前置完成部分满足

pi=maxupreitu.p_i=\max_{u\in\operatorname{pre}_i}t_u.
  • 于是按照编号从小到大计算
ti=max(ri,pi)t_i=\max(r_i,p_i)

即可。时间复杂度为 O(n+m+s)\mathcal O(n+m+s)

【部分分:测试点 12∼1512\sim 1512∼15】

  • 回到一般图。对每个区域 ii 再维护未完成前置条件的数量 cic_i
  • 只有当 ci=0c_i=0rir_i 有限时,区域 ii 才成为候选区域,其候选时刻为 max(ri,pi)\max(r_i,p_i)
  • 每次线性扫描所有候选区域,取候选时刻最小者。进入后同时松弛出航路,并通知所有把它作为前置条件的区域。
  • 这已经是完整算法,只是尚未使用堆。时间复杂度为 O(n2+m+s)\mathcal O(n^2+m+s)

【正解】

  • 对区域 ii 维护三项信息:

  • rir_i:已经完成的区域沿航路抵达 ii 的最早时刻。

  • pip_i:已经完成的前置区域中,完成时刻的最大值。

  • cic_i:尚未完成的前置区域数量。

  • 初始时 r1=0r_1=0,其余 ri=+r_i=+\infty。所有 pi=0p_i=0cic_i 等于输入给出的前置数量。

  • ci=0c_i=0ri<+r_i<+\infty 时,把

(max(ri,pi),i)(\max(r_i,p_i),i)

放入小根堆。

  • 从堆中取出候选时刻最小的区域 uu。若该记录已经过期,或者 uu 已经进入,直接丢弃。
  • 否则令
tu=max(ru,pu),t_u=\max(r_u,p_u),

并确定区域 uu 的最早进入时刻。

  • 对每条航路 uvu\to v,执行
rvmin(rv,tu+wu,v).r_v\gets\min(r_v,t_u+w_{u,v}).

若此时 cv=0c_v=0,就把新的候选时刻放入堆。

  • 对每个把 uu 作为前置区域的 vv,执行
pvmax(pv,tu),cvcv1.p_v\gets\max(p_v,t_u),\qquad c_v\gets c_v-1.
  • cvc_v 第一次变为 00 时,全部前置区域已经完成。若 rvr_v 有限,就把
(max(rv,pv),v)(\max(r_v,p_v),v)

放入堆。

  • 航路更新可能多次改善 rvr_v,所以堆中允许保留旧记录。取出时比较当前值即可忽略过期记录。

  • 证明仍然沿用 Dijkstra 的贪心顺序。设当前堆中最小候选时刻为 TT,对应区域 uu

  • 任何尚未进入的区域,其真正进入时刻都不小于当前候选时刻。若一条尚未处理的路径将来抵达 uu,它必须先经过某个尚未进入的区域,再经过非负边权航路,因此抵达时刻不可能小于 TT

  • uu 的全部前置区域已经完成,pup_u 也不会再改变。故 max(ru,pu)=T\max(r_u,p_u)=T 已经是最终答案,可以安全确定。

  • 归纳处理所有区域,算法正确。

  • 每条航路只在起点进入时松弛一次,每条前置关系只在前置区域进入时处理一次。

  • 每次有效更新至多向堆中加入一条记录,堆规模为 O(n+m+s)\mathcal O(n+m+s)

  • 时间复杂度为

O((n+m+s)logn),\mathcal O((n+m+s)\log n),

空间复杂度为 O(n+m+s)\mathcal O(n+m+s)

  • 关键始终是区分“信号已经抵达星门”和“星门已经允许进入”,两项时间分别取最小值与最大值,最后再取较大者。

【参考代码】

/*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;}