和 Grass Cownoiseur 很相似,
对于经过一条边
<
u
,
v
>
<u,v>
<u,v> 的最长链长度应该等于
f
[
u
]
+
g
[
v
]
+
1
f[u]+g[v]+1
f[u]+g[v]+1 ,
其中
f
[
u
]
f[u]
f[u] 表示以
u
u
u 为终点的最长路即任意一点到
u
u
u 的最长路,
g
[
v
]
g[v]
g[v] 表示以
v
v
v 为起点的最长路即
v
v
v 任意一点到 的最长路
记
A
A
A 为拓扑序小于当前枚举结点
i
i
i 的元素集合
记
B
B
B 为拓扑序大于当前枚举结点
i
i
i 的元素集合
初始所有点都在
B
B
B 中
若删去
i
i
i ,
A
A
A 中的所有
f
f
f ,与
B
B
B 中的所有
g
g
g 都是不受影响的
如上图,若考虑删去
4
4
4 号点
应将 4 4 4 号点从 B B B 移出
而 < 3 , 4 > < 1 , 4 > < 4 , 5 > <3,4>\space <1,4> \space <4,5> <3,4> <1,4> <4,5> 这些边会受到影响
例如,删除
<
3
,
4
>
<3,4>
<3,4>,则应该删除一条长度为
f
[
3
]
+
1
+
g
[
4
]
f[3]+1+g[4]
f[3]+1+g[4] 的链
将所有受影响边删除
在所有受影响的边被删除后,我们需要得出其余边中的最长链
而在计算完当前点之后,我们应将所有受影响的被删除边再重新加回图中,并将当前节点放入
A
A
A 中
我们操作需要支持 插入、删除、求最大值
可使用 multiset
(multiset 入门)
做法:
- 先预处理出每个点的 f [ i ] , g [ i ] f[i],g[i] f[i],g[i]
- 将所有点的 g [ i ] g[i] g[i] 插入 multiset 中 (初始状态所有点都在集合 B 中)
- 按照拓扑序枚举所有点 i i i,将 g [ i ] g[i] g[i] 删除,再将受 i i i 影响的边删除,之后将 f [ u ] f[u] f[u] 插入 ,将所有受影响边重新插入
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,m,ans=1e9+5,anspos;
int indeg1[MAXN],indeg2[MAXN];
int f[MAXN],g[MAXN],id[MAXN],cnt;
vector <int> G1[MAXN],G2[MAXN];
queue <int> q;
void Topo1()
{
for(int i=1;i<=n;i++)
if(indeg1[i]==0)
{
q.push(i);
id[++cnt]=i;
}
while(!q.empty())
{
int u=q.front(); q.pop();
for(auto v:G1[u])
{
indeg1[v]--;
f[v]=max(f[v],f[u]+1);
if(indeg1[v]==0)
{
id[++cnt]=v;
q.push(v);
}
}
}
}
void Topo2()
{
for(int i=1;i<=n;i++)
if(indeg2[i]==0) q.push(i);
while(!q.empty())
{
int u=q.front(); q.pop();
for(auto v:G2[u])
{
indeg2[v]--;
g[v]=max(g[v],g[u]+1);
if(indeg2[v]==0) q.push(v);
}
}
}
multiset <int> s;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
G1[u].push_back(v); indeg1[v]++;
G2[v].push_back(u); indeg2[u]++;
}
Topo1(); Topo2();
for(int i=1;i<=n;i++) s.insert(g[i]);
for(int i=1;i<=n;i++)
{
int u=id[i];
s.erase(s.find(g[u]));
for(auto v:G2[u]) // v-->u
s.erase(s.find(f[v]+1+g[u]));
if(*--s.end()<ans)
{
ans=*--s.end();
anspos=u;
}
s.insert(f[u]);
for(auto v:G1[u]) // u-->v
s.insert(f[u]+1+g[v]);
}
cout<<anspos<<" "<<ans;
return 0;
}