P3573 [POI 2014] RAJ-Rally 做题记录
Description
给定一个 \(n\) 个点 \(m\) 条边的有向无环图,每条边长度都是 \(1\)。
请找到一个点,使得删掉这个点后剩余的图中的最长路径最短。
\(2\le n\le5\times10^5\),\(1\le m\le10^6\)。
Solution
如果不删掉任何点,答案怎么求呢?
注意到给出的图是一个 DAG,那么我们建立一个超级源点 \(S\) 和一个超级汇点 \(T\),可以用正反两遍拓扑排序 \(O(n)\) 求出每个点的 \(dis(S\rightarrow i)\) 与 \(dis(i \rightarrow T)\)。
我们令 \(dis(S\rightarrow S)=dis(T\rightarrow T)=-1\),我们枚举所有边 \((u\rightarrow v)\),答案就是 \(dis(S\rightarrow u)+dis(v\rightarrow T)+1\) 的最大值。
如果我们删掉了一个点,答案又该怎么求呢?
我们在拓扑排序中处理出拓扑序。删掉点 \(x\) 之后,点被分为两部分:拓扑序在 \(x\) 之前的点集 \(X\),拓扑序在 \(x\) 之后的点集 \(Y\)。
那么答案就有三种情况:
- \(\max_{p\in X}(dis(S\rightarrow p))\);
- \(\max_{p\in Y}(dis(p\rightarrow T))\);
- \(\max_{p\in X,q\in Y} (dis(S\rightarrow p)+dis(q\rightarrow T)+1)\),\((p\rightarrow q)\in E\)。
由于 \(Y\) 中的点的拓扑序都在 \(X\) 中的点之后, 所以不存在一条边 \((u\rightarrow v)\),使得 \(u\in Y,v\in X\)。
前两种情况是好处理的,但第三种呢?
我们拿出拓扑序最小的点 \(u\),其答案为 \(\max_{p\ne u} (dis(p\rightarrow T))\)。
假如我们已经求出了拓扑序在 \([1,i-1]\) 中的点的答案。我们把 \(X,Y\) 两个集合维护出来,现在 \(X\) 中的点拓扑序都小于 \(i\),\(Y\) 中的点都大于等于 \(i\)。
我们把拓扑序为 \(i\) 的这个点 \(v\) 从 \(Y\) 中拿出来。删掉 \(v\) 之后,\(dis(v\rightarrow T)\) 和所有的 \(dis(S\rightarrow z)+dis(v\rightarrow T)+1 \ ((z\rightarrow v)\in E)\) 都作废了。剩余的最大值就是 \(v\) 的答案。
接着,我们把 \(v\) 加入到 \(X\) 中。\(dis(S\rightarrow v)\) 与所有的 \(dis(S\rightarrow v)+dis(z\rightarrow T)+1 \ ((v\rightarrow z)\in E)\) 生效。
我们需要一个支持插入、删除、查询最大值的数据结构。multiset
维护即可。
时间复杂度为 \(O((n+m)\log (n+m))\)。
int n,m;
struct Edge{int to,nxt,tp;
}edge[M<<1];int topo[N],head[N],tot,in[N],dis1[N],dis2[N];void Add(int u,int v,int tp){edge[++tot]={v,head[u],tp};head[u]=tot;
}signed main(){read(n),read(m);for(int i=1;i<=n;i++){Add(0,i,0);in[i]=1;}for(int i=1;i<=m;i++){int u,v;read(u),read(v);Add(u,v,0);Add(v,u,1);in[v]++;}queue<int> q; q.push(0); tot=-1;dis1[0]=-1;while(q.size()){int x=q.front(); q.pop();topo[++tot]=x;for(int i=head[x];i;i=edge[i].nxt){if(edge[i].tp) continue;int t=edge[i].to;Ckmax(dis1[t],dis1[x]+1);if(!(--in[t])) q.push(t);}}for(int i=n;i;i--){int x=topo[i];for(int j=head[x];j;j=edge[j].nxt){if(edge[j].tp) continue;int t=edge[j].to;Ckmax(dis2[x],dis2[t]+1);}}multiset<int> s;for(int i=1;i<=n;i++) s.insert(dis2[i]);int ans=IINF,frm=0;for(int i=1;i<=n;i++){int x=topo[i];for(int j=head[x];j;j=edge[j].nxt){if(!edge[j].tp) continue;int t=edge[j].to;s.erase(s.find(dis2[x]+dis1[t]+1));}s.erase(s.find(dis2[x]));if(s.size()){int res=*s.rbegin();if(res<ans) ans=res,frm=x;}s.insert(dis1[x]);for(int j=head[x];j;j=edge[j].nxt){if(edge[j].tp) continue;int t=edge[j].to;s.insert(dis1[x]+dis2[t]+1);}}printf("%d %d\n",frm,ans);return 0;
}