图论 —— DAG 的覆盖与独立集

【概述】

在 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)(最大独立集)点击这里
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值