hdu-3861(强连通图缩点+二分图最小路径覆盖)

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

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

 

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

定义:

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

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

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

              最小路径覆盖=顶点数-最大匹配数。

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxn=55555;
int  dfn[maxn], low[maxn], stack[maxn], belong[maxn], visit[maxn], match[maxn];
bool instack[maxn];
int top, scnt, Index, n, m, T;
vector<int>vt[maxn];

struct Node
{
    int u, v;//有向边u->v
}f[2*maxn];

void Init_tarjan()
{
    top=scnt=Index=0;
    for(int i=1; i<=n; i++) dfn[i]=low[i]=instack[i]=0;
}

void tarjan(int u)//求该有向图的强连通分量
{
    stack[++top]=u;
    dfn[u]=low[u]=++Index;//DFN(u)为节点u搜索的次序编号(时间戳),
    instack[u]=1;           //Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号
    for(int i=0; i<vt[u].size(); i++)
    {
        int v=vt[u][i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u])
    {
        int v;
        scnt++;
        do
        {
            v=stack[top--];
            instack[v]=0;
            belong[v]=scnt;//scnt值为强连通分量个数
        }
        while(u!=v);
    }
}

bool find(int u)//是否能形成匹配
{
    for(int i=0; i<vt[u].size(); i++)
    {
        int v=vt[u][i];
        if(!visit[v])
        {
            visit[v]=1;
            if(match[v]==-1||find(match[v]))
            {
                match[v]=u;
                return true;
            }
        }
    }
    return false;
}

int hungary()//二分图最大匹配
{
    int cnt=0;
    memset(match,-1,sizeof(match));
    for(int i=1; i<=scnt; i++)
    {
        for(int j=1; j<=scnt; j++) visit[j]=0;
        if(find(i)) cnt++;
    }
    return cnt;
}

int main()
{
    cin >> T;
    while(T--)
    {
        cin >> n >> m;
        for(int i=0; i<=n; i++) vt[i].clear();
        for(int i=0; i<m; i++)
        {
            scanf("%d%d",&f[i].u,&f[i].v);
            vt[f[i].u].push_back(f[i].v);
        }
        Init_tarjan();
        for(int i=1; i<=n; i++)
            if(!dfn[i]) tarjan(i);
        for(int i=0; i<=n; i++) vt[i].clear();
        for(int i=0; i<m; i++)
        {
            int u=belong[f[i].u], v=belong[f[i].v];
            if(u==v) continue;
            vt[u].push_back(v);//缩点,成为弱连通图
        }
        int ans=hungary();
        cout << scnt-ans <<endl;
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值