【概述】
在 DAG 的覆盖与独立集问题中,常见的问题分为三类:
- 最小路径点覆盖
- 最小路径可重复点覆盖
- 最大独立集数
这三类问题都可以利用二分图的匈牙利算法来解决。
【最小路径覆盖】
最小路径覆盖:给定一张有向无环图,要求用尽量少的不相交的简单路径,覆盖有向无环图的所有顶点(每个顶点恰好被覆盖一次)
根据 Koning 定理的推广:DAG 最小路径覆盖 = DAG 顶点数 - 新二分图的最大匹配数
而对于新二分图,是根据原来的 DAG 图 G=(V,E),n=|V| 拆点得来的:
- 将 G 中的每个点 x 拆成编号为 x 与 x+n 的两个点
- 1~n 作为二分图的左部点,n+1~2n 作为二分图的右部点
- 对于原图的每条有向边 (x,y),在二分图的左部点 x 与右部点 y+n 之间连边
- 最终得到的二分图称为 G 的拆点二分图 Gz
简单来说,就是对给出的 n 个点 m 条边的 DAG 图构建成一个 n 个点的邻接矩阵,这个邻接矩阵,就是新的二分图
int n,m;
bool vis[N];
int link[N];
bool G[N][N];
bool dfs(int x){
for(int y=1;y<=m;y++){
if(G[x][y]&&!vis[y]){
vis[y]=true;
if(link[y]==-1 || dfs(link[y])){
link[y]=x;
return true;
}
}
}
return false;
}
int hungarian(){
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,false,sizeof(vis));
if(dfs(i))
ans++;
}
return ans;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){
memset(link,-1,sizeof(link));
memset(G,true,sizeof(G));
while(m--){
int x,y;
scanf("%d%d",&x,&y);
G[x][y]=false;
}
int mate=hungarian();//最大匹配数
int res=n-mate;//最小路径点覆盖
printf("%d\n",res);
}
return 0;
}
【最小路径可重复点覆盖/最大独立集】
最大独立集数:选取最多的点集,使点集中任意两点均不相连
最小路径可重复点覆盖:给定一张有向无环图,要求用尽量少的可相交的简单路径,覆盖有向无环图的所有顶点(一个顶点可被覆盖多次)
在最小路径可重复点覆盖问题中,若两条路径:... -> u -> p -> v -> ... 与 ... -> x -> p -> y -> ... 在点 p 相交,则在原图中添加一条边 (x,y),让第二条路径直接走 x->y,就可以避免重复覆盖点 p
进一步,如果将原图中所有间接连通的点对 x、y 直接连上边 (x,y),那么最小路径可重复点覆盖,一定都能转换成最小路径覆盖
因此,对于一个有向无环图 G 的最小路径可重复点覆盖,等价于对 DAG 求传递闭包后得到的新图 G',再对 G' 上求最小路径点覆盖
而求 DAG 的最大独立集,与求最小路径可重复点覆盖思路相同。
int n,m;
bool vis[N];
int link[N];
bool G[N][N];
bool dfs(int x){
for(int y=1;y<=n;y++){
if(G[x][y]&&!vis[y]){
vis[y]=true;
if(link[y]==-1 || dfs(link[y])){
link[y]=x;
return true;
}
}
}
return false;
}
int hungarian(){
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,false,sizeof(vis));
if(dfs(i))
ans++;
}
return ans;
}
void floyd(){//传递闭包
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
G[i][j]|=G[i][k]&G[k][j];
}
int main(){
int t;
scanf("%d",&t);
while(t--){
memset(link,-1,sizeof(link));
memset(G,false,sizeof(G));
scanf("%d%d",&n,&m);
while(m--){
int x,y;
scanf("%d%d",&x,&y);
G[x][y]=true;
}
floyd();//计算传递闭包
int mate=hungarian();//最大匹配数
int res=n-mate;//最大独立集、最小路径可重复点覆盖
printf("%d\n",res);
}
return 0;
}
【例题】
- Dolls(HDU-4160)(最小路径覆盖):点击这里
- Robots(POJ-1548)(最小路径覆盖):点击这里
- Air Raid(POJ-1422)(最小路径覆盖):点击这里
- Taxi Cab Scheme(POJ-2060)(最小路径覆盖+曼哈顿距离):点击这里
- Repairing Company(POJ-3216)(最小路径覆盖+Floyd求最短路):点击这里
- Harry Potter and the Present II(HDU-3991)(最小路径覆盖+Floyd求最短路):点击这里
- The Maximum Unreachable Node Set(UVALive-8456)(最大独立集):点击这里