题解:P16106 [ICPC 2019 NAIPC] Planes, Trains, but not Automobiles
lailai0916 · · 题解
题意简述
给定一张表示单向铁路的有向无环图,任意两座城市之间都可以乘飞机。选择起点并恰好访问每座城市一次,求最少乘机次数,以及在某条最优路线中可能作为飞机起点或终点的所有城市。
解题思路
将一条旅行路线中的飞机边删去,剩余部分就是若干条互不相交的铁路路径,恰好覆盖全部城市。反过来,任意一组这样的路径都可以用飞机依次连接。因此,若最少需要
把每座城市拆成左右两个点,铁路
初始有 l[u] 和 r[v] 分别记录左侧点、右侧点的匹配对象,最终得到
还需要判断一座城市能否在 某个 最小路径覆盖中成为路径端点,不能仅检查当前匹配得到的端点。
- 左侧点未匹配,表示该城市没有铁路后继,即它是某条路径的终点。
- 右侧点未匹配,表示该城市没有铁路前驱,即它是某条路径的起点。
若
问题就变成:给定一个最大匹配,找出每侧所有 能在某个最大匹配中不匹配的点。
先处理左侧。从当前所有未匹配左侧点出发,沿「非匹配边走到右侧,再沿匹配边返回左侧」的方式搜索。所有能到达的左侧点,都可以变成未匹配点。
这是因为,从某个未匹配左侧点到目标左侧点的交替路径具有偶数条边,非匹配边与匹配边数量相等。将路径上所有边的选取状态取反,匹配大小不变。原起点得到匹配,目标点失去匹配,仍然得到最大匹配。
这个搜索也不会遗漏答案。设另一个最大匹配使左侧点
处理右侧时交换两侧角色,并沿原二分图边的反方向搜索。两次得到的城市集合取并集,就是全部可能使用机场的城市。
mark(G,l,r) 完成左侧搜索。访问左侧点 r[v]。实现没有专门排除 mark(H,r,l) 使用反向邻接表完成右侧搜索。
最大匹配部分的 bfs 记录各个左侧点的层数,以及到达未匹配右侧点的最短层数 dep。dfs 仅沿层数增加的边前进,并且仅在最短层终止增广。cur 保存每个点本轮尚未尝试的边,避免反复扫描失败分支。为了处理长交替路径,st 和 ed 显式保存当前路径,找到终点后倒序改写匹配,不依赖递归深度。
最大匹配的时间复杂度为
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int n,dep;
int l[N],r[N],dis[N],cur[N],st[N],ed[N];
bool vis[N],ans[N];
vector<int> G[N],H[N];
bool bfs()
{
fill(dis,dis+n+1,-1);
queue<int> q;
for(int i=1;i<=n;i++)if(!l[i]){dis[i]=0;q.push(i);}
dep=n+1;
while(!q.empty())
{
int u=q.front();
q.pop();
if(dis[u]>=dep)continue;
for(auto v:G[u])
{
if(!r[v])dep=dis[u]+1;
else if(dis[r[v]]==-1){dis[r[v]]=dis[u]+1;q.push(r[v]);}
}
}
return dep<=n;
}
bool dfs(int u)
{
int top=1;
st[top]=u;
while(top)
{
u=st[top];
if(cur[u]==G[u].size()){dis[u]=-1;top--;continue;}
int v=G[u][cur[u]++];
if(!r[v]&&dis[u]+1==dep)
{
ed[top]=v;
for(int i=top;i>=1;i--){l[st[i]]=ed[i];r[ed[i]]=st[i];}
return 1;
}
if(r[v]&&dis[r[v]]==dis[u]+1)
{
ed[top]=v;
top++;
st[top]=r[v];
}
}
return 0;
}
void mark(vector<int> *G,int *l,int *r)
{
fill(vis,vis+n+1,0);
queue<int> q;
for(int i=1;i<=n;i++)if(!l[i]){vis[i]=1;q.push(i);}
while(!q.empty())
{
int u=q.front();
q.pop();
ans[u]=1;
for(auto v:G[u])
{
if(r[v]&&!vis[r[v]]){vis[r[v]]=1;q.push(r[v]);}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
G[u].push_back(v);
H[v].push_back(u);
}
int cnt=n;
while(bfs())
{
fill(cur,cur+n+1,0);
for(int i=1;i<=n;i++)if(!l[i]&&dfs(i))cnt--;
}
cout<<cnt-1<<'\n';
if(cnt>1)
{
mark(G,l,r);
mark(H,r,l);
for(int i=1;i<=n;i++)if(ans[i])cout<<i<<' ';
}
cout<<'\n';
return 0;
}