hdu 3861(tarjan 缩点 + 二分图匹配 求最小路径覆盖


题意:

题目大意:一个有向图,让你按规则划分区域,要求划分的区域数最少。

规则如下:1、有边u到v以及有边v到u,则u,v必须划分到同一个区域内。2、一个区域内的两点至少要有一方能到达另一方。3、一个点只能划分到一个区域内。

 

解题思路:根据规则1可知必然要对强连通分量进行缩点,缩点后变成了一个弱连通图。根据规则2、3可知即是要求图的最小路径覆盖。

定义:

最小路径覆盖:在图中找一些路径(路径数最少),使之覆盖了图中所有的顶点,且每个顶点有且仅和一条路径有关联。最小路径覆盖=顶点数-最大匹配数。

最小顶点覆盖:在图中找一些点(顶点数最少),使之覆盖了图中所有的边,每条边至少和一个顶点有关联。

二分图:最小顶点覆盖=最大匹配数。


PS:以前没写过有向图的二分匹配,现在知道找增广路的时候match[]只更新一边就行,不用双向更新。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 10;
typedef long long ll;
typedef pair<int,int> P;
P a[maxn];
#define clr(x,y) memset(x,y,sizeof x)
int n,m;
int head[maxn];
struct Edge
{
    Edge(){}
    Edge(int x,int y):to(x),next(y){}
    int to,next;
}edge[maxn];
int edge_num;
int dfn[maxn];
int low[maxn];
int st[maxn];
bool vis[maxn];
int col[maxn];
int cnt;
int top;
bool used[maxn];
int match[maxn];
vector<int>g[10000 + 10];
void Init()
{
    cnt = 0;
    edge_num = 0;
    clr(head,-1);
    clr(dfn,0);
    clr(low,0);
    top = 0;
    clr(vis,false);
    for(int i = 1; i < 10000 + 10; i ++)
        g[i].clear();
}
void add_edge(int x,int y)
{
    edge[edge_num] = Edge(y,head[x]);
    head[x] = edge_num ++;
}

void tarjan(int u,int& len)
{
    low[u] = dfn[u] = len;vis[u] = true;
    st[++ top] = u;
//    cout << u << " " << head[u] << endl;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(!dfn[v])
        {
            len ++;
            tarjan(v,len);
            low[u] = min(low[u],low[v]);
        }
        else if(vis[v])low[u] = min(low[u],dfn[v]);
    }
    if(low[u] == dfn[u])
    {
        vis[u] = false;col[u] = ++ cnt;
//        cout << cnt << "   :" << u << " ";
        while(st[top --] != u)
        {
            int to = st[top + 1];
//            cout << u << " ";
            vis[to] = false;col[to] = cnt;
        }
//        cout << endl;
    }
}
bool dfs(int u)
{
    for(int i = 0; i < g[u].size(); i ++)
    {
        int v = g[u][i];
        if(!used[v])
        {
            used[v] = true;
            if(match[v] == -1 || dfs(match[v]))
            {
                match[v] = u;
                return true;
            }
        }
    }
    return false;
}
int bin_match()
{
    clr(match,-1);
    int ret = 0;
    for(int i = 1; i <= cnt; i ++)
    {
        if(match[i] < 0)
        {
            clr(used,false);
            if(dfs(i))
                ret ++;
        }
    }
    return ret;
}
int main()
{
    int Tcase;
    scanf("%d",&Tcase);
    while(Tcase --)
    {
        Init();
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= m; i ++)
        {
            int x,y;scanf("%d%d",&x,&y);
            a[i] = P(x,y);
            add_edge(x,y);
//            cout << x << " II iII  " << head[x] << endl;
        }
        for(int i = 1; i <= n; i ++)
        {
            if(!dfn[i])
            {
                int len = 1;
                tarjan(i,len);
            }
        }
//        for(int i = 1; i <= n; i ++)
//            cout << i << " " << col[i] << endl;
        for(int i = 1; i <= m; i ++)
        {
            int x = col[a[i].first],y = col[a[i].second];
            if(x != y)
            {
                g[x].push_back(y);
            }
        }
        printf("%d\n",cnt - bin_match());
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值