给定一个N个点M条边的有向无环图,每条边长度都是1。
请找到一个点,使得删掉这个点后剩余的图中的最长路径最短。
2<=N<=500000,1<=M<=1000000
DAG可能有多个起点和终点,所以不妨建S点和T点作为所有点的起点和终点,这样整个图中的最长路就是S到T的最长路。接下来考虑如何动态维护即可。
对于每一个点很容易求出S到它的最长路和它到T的最长路,也就处理出了每一条边两端到S和T的最长路。由此可以发现,一条最长路径上的每一条边,都可以代表这条最长路。
因此我们要动态维护一个边的集合来代表整个图中的最长路状态。考虑删点时,我们只能找到与它相邻的边,因此我们维护的应该是在拓扑排序意义下一个“层”的边集,也就是一个割集,割集决定了它可以表示所有的最长路。在一个”层“中,边与边之间是近似于“平行”的关系,而每一条边就是一个最长路在当前拓扑顺序下的体现。这样,边与边之间是相对独立的,不会出现一条最长路的多个代表元素同时存在的情况。
从具体过程上来说,按照拓扑序枚举每一个点,将这一个点连接的“上一层”的边删除,再将“下一层”的边加入,两个过程中间的状态就恰好是”这个点不在“的状态。
此外,由于删点过程中可能会破坏连通性,所以边集中应维护每个点到一端的最长路。一开始都是到T的,代表”割层在这个点之前“,当到了这个点,就要将到T的删去,加入到S的,表示”割层已经走到了这个点之后“。这样才能保证删边时不对它们产生影响。
注意要按照拓扑序而不是编号来遍历节点,不要打错了。
#include<cstdio>
#include<queue>
#include<ext/pb_ds/priority_queue.hpp>
#define gm 500005
using namespace __gnu_pbds;
struct heap
{
priority_queue<int> h;
int del[gm];
inline void push(int x)
{
if(del[x]) --del[x];
else h.push(x);
}
inline void erase(int x)
{
++del[x];
}
inline int top()
{
int t=h.top();
while(del[t])
{
--del[t];
h.pop();
t=h.top();
}
return t;
}
}h;
struct e
{
int t;
e *n;
e(int t,e *n):t(t),n(n){}
}*f[gm],*g[gm];
int n,m,a,b;
int len=0x7fffffff,ans;
int rd[gm],s[gm],t[gm];
int sa[gm],tot=0;
inline void max(int &a,int b)
{
if(a<b) a=b;
}
inline void sort()
{
static std::queue<int> q;
for(int i=1;i<=n;++i)
if(!rd[i]) s[i]=1,q.push(i);
while(!q.empty())
{
int now=sa[++tot]=q.front();q.pop();
for(e *i=f[now];i;i=i->n)
{
max(s[i->t],s[now]+1);
--rd[i->t];
if(!rd[i->t]) q.push(i->t);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
while(m--)
{
scanf("%d%d",&a,&b);
f[a]=new e(b,f[a]);
g[b]=new e(a,g[b]);
++rd[b];
}
sort();
for(int i=n;i>=1;--i)
{
int x=sa[i];
if(!t[x]) t[x]=1;
for(e *j=f[x];j;j=j->n)
max(t[x],t[j->t]+1);
h.push(t[x]);
}
for(int i=1;i<=n;i++)
{
int x=sa[i];
for(e *j=g[x];j;j=j->n)
h.erase(s[j->t]+t[x]);
h.erase(t[x]);
int tp=h.top();
if(tp<len) len=tp,ans=x;
for(e *j=f[x];j;j=j->n)
h.push(s[x]+t[j->t]);
h.push(s[x]);
}
printf("%d %d\n",ans,len-1);
return 0;
}