题解:P15690 [ICPC 2023 Jakarta R] Contingency Plan 2
核心判断:一个 DAG 存在唯一的拓扑序当且仅当存在一条经过所有节点的有向哈密顿路径。
::::info[哈密顿路径的定义以及这条结论的证明] 哈密顿路径:一条经过每个点恰好一次的路径。
充分性:
假设这个唯一的拓扑序是
所以有唯一的拓扑序
必要性:
假设存在
| 综上,一个 DAG 存在唯一的拓扑序当且仅当存在一条经过所有节点的有向哈密顿路径 成立。 |
|---|
定义一条经过若干节点的有向路径是链。
那么经过所有节点的有向哈密顿路径可以由多条链加上一些边超级拼装出来,所以这就启示我们先跑一遍最小链覆盖后将这些链连接起来。因此第一问的答案的下届就是最小链覆盖数
但是构造方案时链与链之间不能随便连接,假如存在一条边连接链
按照上面的情况我们把链当作节点,连接不同链的边保留就弄出来一个 DAG(原无向图保证是棵树,所以保留下来的边不可能有环),然后我们跑拓扑排序顺着拓扑序连边就做完了。时间复杂度
::::info[最小链覆盖的 dinic 实现] 叠甲说明:作者太菜了不会 Hopcroft-Karp,所以我是用 dinic 算法实现的。
最小链覆盖数
构造如下:
因为一条链中的节点最多一个前驱和一个后继,所以我们构造二分图来保证:
- 每个左部点最多匹配一次来选择至多一个后继。
- 每个右部点最多匹配一次来选择至多一个前驱。
因此定义左部点
所以对于每一个原图中的有向边
怎么还原链:
定义
| 如果 |
|---|
::::success[code(dinic 算法)]
#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
#define FOR(i,a,b) for(int i=(a);i<=(int)b;i++)
#define ROF(i,a,b) for(int i=(b);i>=(int)a;i--)
const int N=2e5+10;
const ll inf=1e18+10;
const ll mod=1e9+7;
const double eps=1e-7;
using Vl=vector<int>;
using PI=array<ll,2>;
int n,S,T,h[N<<1],now[N<<1],dep[N<<1],cte=1,q[N<<1],eu[N],ev[N],bl[N],L[N],R[N],st[N],ed[N],in[N],eid[N];
struct E{int v,nxt,w;}e[N*6];
inline int Ae(int u,int v,int w){e[++cte]={v,h[u],w};return h[u]=cte;}
inline bool bfs(){
fill(dep,dep+T+1,mod);
int l=1,r=0;dep[q[++r]=S]=0;now[S]=h[S];
while(l<=r){
int u=q[l++];
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].w&&dep[v]==mod){
now[v]=h[v];dep[q[++r]=v]=dep[u]+1;
if(v==T) return 1;
}
}
}return 0;
}
inline int dfs(int u,int res){
if(u==T) return res;
int ct=0;
for(int &i=now[u],v;i;i=e[i].nxt) if(e[i].w&&(dep[v=e[i].v]==dep[u]+1)){
int k=dfs(v,min(e[i].w,res));
if(!k) dep[v]=mod;
e[i].w-=k,e[i^1].w+=k,ct+=k,res-=k;
if(!res) break;
}return ct;
}
Vl g[N];
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);
// cin.tie(0);cout.tie(0);
cin>>n;
FOR(i,1,n-1) cin>>eu[i]>>ev[i];
S=0,T=2*n+1;
FOR(i,1,n) Ae(S,i,1),Ae(i,S,0),Ae(i+n,T,1),Ae(T,i+n,0);
FOR(i,1,n-1) eid[i]=Ae(eu[i],ev[i]+n,1),Ae(ev[i]+n,eu[i],0);// 建边:以 u 结尾的链 和 以 v 开头的链 链接起来
while(bfs()) dfs(S,mod);
FOR(i,1,n-1) if(!e[eid[i]].w) L[eu[i]]=ev[i],R[ev[i]]=eu[i];// L[u] 左部点 u 匹配的右部点,R[u] 右部点 u 匹配的左部点
int ct=0;
FOR(i,1,n) if(!R[i]){//一个链的开头
st[++ct]=i;int u=i;
while(u) ed[bl[u]=ct]=u,u=L[u];
}
FOR(i,1,n-1){
int u=bl[eu[i]],v=bl[ev[i]];
if(u^v) g[u].push_back(v),++in[v];
}
int l=1,r=0;
FOR(i,1,ct) if(!in[i]) q[++r]=i;
Vl dfn;
while(l<=r){
int u=q[l++];dfn.push_back(u);
for(int v:g[u]) if(!--in[v]) q[++r]=v;
}
cout<<--ct<<'\n';
FOR(i,1,ct) cout<<ed[dfn[i-1]]<<' '<<st[dfn[i]]<<'\n';
return 0;
}
::::